clay-server 2.27.0-beta.13 → 2.27.0-beta.14
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/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +11 -124
- package/lib/mates-identity.js +132 -0
- package/lib/mates-knowledge.js +113 -0
- package/lib/mates-prompts.js +398 -0
- package/lib/mates.js +40 -599
- package/lib/public/app.js +2 -0
- package/lib/public/modules/app-connection.js +4 -4
- package/lib/public/modules/app-header.js +3 -3
- package/lib/public/modules/app-messages.js +4 -0
- package/lib/public/modules/app-projects.js +8 -0
- package/lib/public/modules/input.js +30 -20
- package/lib/public/modules/scheduler-config.js +1532 -0
- package/lib/public/modules/scheduler-history.js +79 -0
- package/lib/public/modules/scheduler.js +33 -1554
- package/lib/sdk-bridge.js +26 -707
- package/lib/sdk-message-processor.js +573 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/users-auth.js +146 -0
- package/lib/users-permissions.js +118 -0
- package/lib/users-preferences.js +210 -0
- package/lib/users.js +48 -398
- package/lib/ws-schema.js +498 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler config module — Create/edit modal, delete dialog, cron builder, preview.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from scheduler.js to keep module sizes manageable.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { showToast } from './utils.js';
|
|
8
|
+
|
|
9
|
+
// Constants (duplicated from scheduler.js — small arrays)
|
|
10
|
+
var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
11
|
+
var MONTH_NAMES = [
|
|
12
|
+
"January", "February", "March", "April", "May", "June",
|
|
13
|
+
"July", "August", "September", "October", "November", "December"
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// Module-private state
|
|
17
|
+
var configCtx = null;
|
|
18
|
+
var previewEl = null;
|
|
19
|
+
var createEditingRecId = null;
|
|
20
|
+
var createSelectedDate = null;
|
|
21
|
+
var createRecurrence = "none";
|
|
22
|
+
var createCustomConfirmed = false;
|
|
23
|
+
var createInterval = "none";
|
|
24
|
+
var createIntervalCustom = null;
|
|
25
|
+
var createIntervalEnd = "allday";
|
|
26
|
+
var createIntervalEndAfter = 5;
|
|
27
|
+
var createIntervalEndTime = "";
|
|
28
|
+
var createColor = "#ffb86c";
|
|
29
|
+
var createEndType = "never";
|
|
30
|
+
var createEndDate = null;
|
|
31
|
+
var createEndCalMonth = null;
|
|
32
|
+
var createEndAfter = 10;
|
|
33
|
+
|
|
34
|
+
// --- Init ---
|
|
35
|
+
|
|
36
|
+
export function initSchedulerConfig(_configCtx) {
|
|
37
|
+
configCtx = _configCtx;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- Create Popover (inline, Akiflow-style) ---
|
|
41
|
+
|
|
42
|
+
export function setupCreateModal() {
|
|
43
|
+
var createPopover = configCtx.getCreatePopover();
|
|
44
|
+
if (!createPopover) return;
|
|
45
|
+
|
|
46
|
+
// Close
|
|
47
|
+
document.getElementById("sched-create-cancel").addEventListener("click", function () { closeCreateModal(); });
|
|
48
|
+
|
|
49
|
+
// Color picker
|
|
50
|
+
var colorBtn = document.getElementById("sched-create-color-btn");
|
|
51
|
+
var colorPalette = document.getElementById("sched-create-color-palette");
|
|
52
|
+
if (colorBtn && colorPalette) {
|
|
53
|
+
colorBtn.addEventListener("click", function (e) {
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
colorPalette.classList.toggle("hidden");
|
|
56
|
+
});
|
|
57
|
+
var swatches = colorPalette.querySelectorAll(".sched-color-swatch");
|
|
58
|
+
for (var i = 0; i < swatches.length; i++) {
|
|
59
|
+
swatches[i].addEventListener("click", function (e) {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
var c = this.dataset.color;
|
|
62
|
+
createColor = c;
|
|
63
|
+
var dot = document.getElementById("sched-create-color-dot");
|
|
64
|
+
if (dot) dot.style.background = c;
|
|
65
|
+
// update active state
|
|
66
|
+
var all = colorPalette.querySelectorAll(".sched-color-swatch");
|
|
67
|
+
for (var j = 0; j < all.length; j++) {
|
|
68
|
+
all[j].classList.toggle("active", all[j].dataset.color === c);
|
|
69
|
+
}
|
|
70
|
+
colorPalette.classList.add("hidden");
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Date picker change -> sync createSelectedDate and recurrence labels
|
|
76
|
+
var datePickerEl = document.getElementById("sched-create-date-picker");
|
|
77
|
+
if (datePickerEl) {
|
|
78
|
+
datePickerEl.addEventListener("change", function () {
|
|
79
|
+
var parts = this.value.split("-");
|
|
80
|
+
if (parts.length === 3) {
|
|
81
|
+
createSelectedDate = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
|
|
82
|
+
document.getElementById("sched-create-date").value = this.value;
|
|
83
|
+
updateRecurrenceLabels(createSelectedDate);
|
|
84
|
+
enforceMinTime();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Time input change -- enforce min time when today is selected
|
|
90
|
+
var timeInputEl = document.getElementById("sched-create-time");
|
|
91
|
+
if (timeInputEl) {
|
|
92
|
+
timeInputEl.addEventListener("blur", function () {
|
|
93
|
+
enforceMinTime();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Task dropdown
|
|
98
|
+
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
99
|
+
var taskList = document.getElementById("sched-create-task-list");
|
|
100
|
+
if (taskBtn && taskList) {
|
|
101
|
+
taskBtn.addEventListener("click", function (e) {
|
|
102
|
+
e.stopPropagation();
|
|
103
|
+
taskList.classList.toggle("hidden");
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Close task dropdown on outside click
|
|
108
|
+
document.addEventListener("click", function (e) {
|
|
109
|
+
var tl = document.getElementById("sched-create-task-list");
|
|
110
|
+
if (tl && !tl.classList.contains("hidden")) {
|
|
111
|
+
if (!tl.contains(e.target) && !e.target.closest("#sched-create-task-btn")) {
|
|
112
|
+
tl.classList.add("hidden");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Recurrence button -> toggle dropdown
|
|
118
|
+
document.getElementById("sched-create-recurrence-btn").addEventListener("click", function (e) {
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
var dd = document.getElementById("sched-create-recurrence-dropdown");
|
|
121
|
+
var btn = document.getElementById("sched-create-recurrence-btn");
|
|
122
|
+
// Close interval dropdown if open
|
|
123
|
+
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
124
|
+
if (dd) {
|
|
125
|
+
var wasHidden = dd.classList.contains("hidden");
|
|
126
|
+
dd.classList.toggle("hidden");
|
|
127
|
+
if (wasHidden && btn) {
|
|
128
|
+
var bRect = btn.getBoundingClientRect();
|
|
129
|
+
var ddW = 280;
|
|
130
|
+
var ddLeft = bRect.left;
|
|
131
|
+
if (ddLeft + ddW > window.innerWidth - 10) ddLeft = window.innerWidth - ddW - 10;
|
|
132
|
+
if (ddLeft < 10) ddLeft = 10;
|
|
133
|
+
dd.style.left = ddLeft + "px";
|
|
134
|
+
dd.style.top = (bRect.bottom + 4) + "px";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// --- Interval button + dropdown ---
|
|
140
|
+
var intervalSnapshot = null;
|
|
141
|
+
document.getElementById("sched-create-interval-btn").addEventListener("click", function (e) {
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
var dd = document.getElementById("sched-create-interval-dropdown");
|
|
144
|
+
var btn = document.getElementById("sched-create-interval-btn");
|
|
145
|
+
// Close recurrence dropdown if open
|
|
146
|
+
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
147
|
+
if (dd) {
|
|
148
|
+
var wasHidden = dd.classList.contains("hidden");
|
|
149
|
+
dd.classList.toggle("hidden");
|
|
150
|
+
if (wasHidden && btn) {
|
|
151
|
+
// Save snapshot for cancel
|
|
152
|
+
intervalSnapshot = {
|
|
153
|
+
interval: createInterval,
|
|
154
|
+
custom: createIntervalCustom ? { value: createIntervalCustom.value, unit: createIntervalCustom.unit } : null,
|
|
155
|
+
end: createIntervalEnd,
|
|
156
|
+
endAfter: createIntervalEndAfter,
|
|
157
|
+
endTime: createIntervalEndTime
|
|
158
|
+
};
|
|
159
|
+
var bRect = btn.getBoundingClientRect();
|
|
160
|
+
var ddW = 220;
|
|
161
|
+
var ddLeft = bRect.left;
|
|
162
|
+
if (ddLeft + ddW > window.innerWidth - 10) ddLeft = window.innerWidth - ddW - 10;
|
|
163
|
+
if (ddLeft < 10) ddLeft = 10;
|
|
164
|
+
dd.style.left = ddLeft + "px";
|
|
165
|
+
dd.style.top = (bRect.bottom + 4) + "px";
|
|
166
|
+
// Auto-apply custom interval when opening
|
|
167
|
+
if (createInterval === "none") {
|
|
168
|
+
applyInlineInterval();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Interval Cancel button - revert to snapshot
|
|
175
|
+
document.getElementById("sched-interval-cancel").addEventListener("click", function (e) {
|
|
176
|
+
e.stopPropagation();
|
|
177
|
+
if (intervalSnapshot) {
|
|
178
|
+
createInterval = intervalSnapshot.interval;
|
|
179
|
+
createIntervalCustom = intervalSnapshot.custom;
|
|
180
|
+
createIntervalEnd = intervalSnapshot.end;
|
|
181
|
+
createIntervalEndAfter = intervalSnapshot.endAfter;
|
|
182
|
+
createIntervalEndTime = intervalSnapshot.endTime;
|
|
183
|
+
updateIntervalBtn();
|
|
184
|
+
}
|
|
185
|
+
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Interval OK button - accept and close
|
|
189
|
+
document.getElementById("sched-interval-ok").addEventListener("click", function (e) {
|
|
190
|
+
e.stopPropagation();
|
|
191
|
+
applyInlineInterval();
|
|
192
|
+
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Interval custom input
|
|
196
|
+
var intCustomValue = document.getElementById("sched-interval-custom-value");
|
|
197
|
+
var intUnitSegs = document.querySelectorAll(".sched-interval-seg");
|
|
198
|
+
function getIntervalUnit() {
|
|
199
|
+
for (var s = 0; s < intUnitSegs.length; s++) {
|
|
200
|
+
if (intUnitSegs[s].classList.contains("active")) return intUnitSegs[s].dataset.unit;
|
|
201
|
+
}
|
|
202
|
+
return "minute";
|
|
203
|
+
}
|
|
204
|
+
function applyInlineInterval() {
|
|
205
|
+
var v = parseInt(intCustomValue.value, 10) || 1;
|
|
206
|
+
var u = getIntervalUnit();
|
|
207
|
+
createInterval = "custom";
|
|
208
|
+
createIntervalCustom = { value: v, unit: u };
|
|
209
|
+
updateIntervalBtn();
|
|
210
|
+
}
|
|
211
|
+
intCustomValue.addEventListener("change", applyInlineInterval);
|
|
212
|
+
for (var si = 0; si < intUnitSegs.length; si++) {
|
|
213
|
+
(function (seg) {
|
|
214
|
+
seg.addEventListener("click", function (e) {
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
for (var s = 0; s < intUnitSegs.length; s++) {
|
|
217
|
+
intUnitSegs[s].classList.toggle("active", intUnitSegs[s] === seg);
|
|
218
|
+
}
|
|
219
|
+
applyInlineInterval();
|
|
220
|
+
});
|
|
221
|
+
})(intUnitSegs[si]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Interval end condition options
|
|
225
|
+
var iendOpts = document.querySelectorAll(".sched-interval-end-opt");
|
|
226
|
+
for (var ie = 0; ie < iendOpts.length; ie++) {
|
|
227
|
+
(function (opt) {
|
|
228
|
+
opt.addEventListener("click", function (e) {
|
|
229
|
+
e.stopPropagation();
|
|
230
|
+
var val = opt.dataset.iend;
|
|
231
|
+
for (var j = 0; j < iendOpts.length; j++) {
|
|
232
|
+
iendOpts[j].classList.toggle("active", iendOpts[j] === opt);
|
|
233
|
+
}
|
|
234
|
+
createIntervalEnd = val;
|
|
235
|
+
var afterRow = document.getElementById("sched-interval-end-after-row");
|
|
236
|
+
var untilRow = document.getElementById("sched-interval-end-until-row");
|
|
237
|
+
if (afterRow) afterRow.classList.toggle("hidden", val !== "after");
|
|
238
|
+
if (untilRow) untilRow.classList.toggle("hidden", val !== "until");
|
|
239
|
+
});
|
|
240
|
+
})(iendOpts[ie]);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
var iendAfterInput = document.getElementById("sched-interval-end-after");
|
|
244
|
+
if (iendAfterInput) {
|
|
245
|
+
iendAfterInput.addEventListener("change", function () {
|
|
246
|
+
createIntervalEndAfter = parseInt(this.value, 10) || 5;
|
|
247
|
+
if (createIntervalEndAfter < 1) createIntervalEndAfter = 1;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
var iendTimeInput = document.getElementById("sched-interval-end-time");
|
|
252
|
+
if (iendTimeInput) {
|
|
253
|
+
iendTimeInput.addEventListener("change", function () {
|
|
254
|
+
createIntervalEndTime = this.value;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Custom repeat: cancel
|
|
259
|
+
document.getElementById("sched-custom-cancel").addEventListener("click", function (e) {
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Custom repeat: unit change
|
|
265
|
+
document.getElementById("sched-custom-unit").addEventListener("change", function () {
|
|
266
|
+
var dowSection = document.getElementById("sched-custom-dow-section");
|
|
267
|
+
if (dowSection) dowSection.style.display = this.value === "week" ? "" : "none";
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Custom repeat: DOW toggle
|
|
271
|
+
var customDowBtns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn");
|
|
272
|
+
for (var i = 0; i < customDowBtns.length; i++) {
|
|
273
|
+
(function (btn) {
|
|
274
|
+
btn.addEventListener("click", function (e) { e.stopPropagation(); btn.classList.toggle("active"); });
|
|
275
|
+
})(customDowBtns[i]);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Custom repeat: End type JS dropdown
|
|
279
|
+
var endBtn = document.getElementById("sched-custom-end-btn");
|
|
280
|
+
var endList = document.getElementById("sched-custom-end-list");
|
|
281
|
+
|
|
282
|
+
endBtn.addEventListener("click", function (e) {
|
|
283
|
+
e.stopPropagation();
|
|
284
|
+
if (endList.classList.contains("hidden")) {
|
|
285
|
+
var r = endBtn.getBoundingClientRect();
|
|
286
|
+
endList.style.left = r.left + "px";
|
|
287
|
+
endList.style.top = (r.bottom + 4) + "px";
|
|
288
|
+
// If it would overflow bottom, show above
|
|
289
|
+
endList.classList.remove("hidden");
|
|
290
|
+
var lr = endList.getBoundingClientRect();
|
|
291
|
+
if (lr.bottom > window.innerHeight - 8) {
|
|
292
|
+
endList.style.top = (r.top - lr.height - 4) + "px";
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
endList.classList.add("hidden");
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
var endItems = endList.querySelectorAll(".sched-custom-end-item");
|
|
300
|
+
for (var ei = 0; ei < endItems.length; ei++) {
|
|
301
|
+
(function (item) {
|
|
302
|
+
item.addEventListener("click", function (e) {
|
|
303
|
+
e.stopPropagation();
|
|
304
|
+
var val = item.dataset.value;
|
|
305
|
+
createEndType = val;
|
|
306
|
+
document.getElementById("sched-custom-end").value = val;
|
|
307
|
+
document.getElementById("sched-custom-end-label").textContent = item.textContent;
|
|
308
|
+
|
|
309
|
+
// Update active state
|
|
310
|
+
for (var j = 0; j < endItems.length; j++) {
|
|
311
|
+
endItems[j].classList.toggle("active", endItems[j] === item);
|
|
312
|
+
}
|
|
313
|
+
endList.classList.add("hidden");
|
|
314
|
+
|
|
315
|
+
// Toggle conditional inputs
|
|
316
|
+
var dateBtn2 = document.getElementById("sched-custom-end-date-btn");
|
|
317
|
+
var afterWrap = document.getElementById("sched-custom-end-after-wrap");
|
|
318
|
+
var calPanel = document.getElementById("sched-custom-end-calendar");
|
|
319
|
+
|
|
320
|
+
dateBtn2.classList.add("hidden");
|
|
321
|
+
afterWrap.classList.add("hidden");
|
|
322
|
+
calPanel.classList.add("hidden");
|
|
323
|
+
|
|
324
|
+
if (val === "until") {
|
|
325
|
+
dateBtn2.classList.remove("hidden");
|
|
326
|
+
if (!createEndDate) {
|
|
327
|
+
createEndDate = new Date(createSelectedDate || new Date());
|
|
328
|
+
createEndDate.setMonth(createEndDate.getMonth() + 1);
|
|
329
|
+
}
|
|
330
|
+
updateEndDateLabel();
|
|
331
|
+
} else if (val === "after") {
|
|
332
|
+
afterWrap.classList.remove("hidden");
|
|
333
|
+
document.getElementById("sched-custom-end-after").value = createEndAfter;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
})(endItems[ei]);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Close end dropdown on outside click
|
|
340
|
+
document.addEventListener("click", function (e) {
|
|
341
|
+
if (endList && !endList.classList.contains("hidden")) {
|
|
342
|
+
if (!endList.contains(e.target) && !endBtn.contains(e.target)) {
|
|
343
|
+
endList.classList.add("hidden");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Custom repeat: End date button -> toggle inline calendar
|
|
349
|
+
document.getElementById("sched-custom-end-date-btn").addEventListener("click", function (e) {
|
|
350
|
+
e.stopPropagation();
|
|
351
|
+
var calPanel = document.getElementById("sched-custom-end-calendar");
|
|
352
|
+
if (calPanel.classList.contains("hidden")) {
|
|
353
|
+
createEndCalMonth = new Date(createEndDate.getFullYear(), createEndDate.getMonth(), 1);
|
|
354
|
+
renderEndCalendar();
|
|
355
|
+
calPanel.classList.remove("hidden");
|
|
356
|
+
try { lucide.createIcons({ node: calPanel }); } catch (ex) {}
|
|
357
|
+
} else {
|
|
358
|
+
calPanel.classList.add("hidden");
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Custom repeat: End calendar prev/next
|
|
363
|
+
document.getElementById("sched-cal-prev").addEventListener("click", function (e) {
|
|
364
|
+
e.stopPropagation();
|
|
365
|
+
createEndCalMonth.setMonth(createEndCalMonth.getMonth() - 1);
|
|
366
|
+
renderEndCalendar();
|
|
367
|
+
});
|
|
368
|
+
document.getElementById("sched-cal-next").addEventListener("click", function (e) {
|
|
369
|
+
e.stopPropagation();
|
|
370
|
+
createEndCalMonth.setMonth(createEndCalMonth.getMonth() + 1);
|
|
371
|
+
renderEndCalendar();
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Custom repeat: After occurrences input
|
|
375
|
+
document.getElementById("sched-custom-end-after").addEventListener("change", function () {
|
|
376
|
+
createEndAfter = parseInt(this.value, 10) || 10;
|
|
377
|
+
if (createEndAfter < 1) { createEndAfter = 1; this.value = 1; }
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Custom repeat: OK
|
|
381
|
+
document.getElementById("sched-custom-ok").addEventListener("click", function (e) {
|
|
382
|
+
e.stopPropagation();
|
|
383
|
+
createRecurrence = "custom";
|
|
384
|
+
createCustomConfirmed = true;
|
|
385
|
+
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
386
|
+
updateRecurrenceBtn();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Run mode toggle (single vs multi-round)
|
|
390
|
+
var runModeContainer = createPopover.querySelector(".sched-create-run-mode");
|
|
391
|
+
if (runModeContainer) {
|
|
392
|
+
runModeContainer.addEventListener("click", function (e) {
|
|
393
|
+
var btn = e.target.closest(".sched-run-mode-btn");
|
|
394
|
+
if (!btn) return;
|
|
395
|
+
var mode = btn.dataset.mode;
|
|
396
|
+
var btns = runModeContainer.querySelectorAll(".sched-run-mode-btn");
|
|
397
|
+
for (var i = 0; i < btns.length; i++) btns[i].classList.toggle("active", btns[i] === btn);
|
|
398
|
+
var iterGroup = document.getElementById("sched-create-iter-group");
|
|
399
|
+
if (iterGroup) iterGroup.classList.toggle("hidden", mode !== "multi");
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Submit
|
|
404
|
+
document.getElementById("sched-create-submit").addEventListener("click", function () { submitCreateSchedule(); });
|
|
405
|
+
|
|
406
|
+
// Delete button -> close popover, then open dialog
|
|
407
|
+
var deleteBtn = document.getElementById("sched-create-delete");
|
|
408
|
+
var deleteDialog = document.getElementById("sched-delete-dialog");
|
|
409
|
+
if (deleteBtn) {
|
|
410
|
+
deleteBtn.addEventListener("click", function (e) {
|
|
411
|
+
e.stopPropagation();
|
|
412
|
+
if (!createEditingRecId) return;
|
|
413
|
+
var records = configCtx.getRecords();
|
|
414
|
+
var rec = null;
|
|
415
|
+
for (var j = 0; j < records.length; j++) {
|
|
416
|
+
if (records[j].id === createEditingRecId) { rec = records[j]; break; }
|
|
417
|
+
}
|
|
418
|
+
if (!rec) return;
|
|
419
|
+
// Save context before closing popover
|
|
420
|
+
var deleteRecId = createEditingRecId;
|
|
421
|
+
var deleteDate = createSelectedDate ? new Date(createSelectedDate) : null;
|
|
422
|
+
closeCreateModal();
|
|
423
|
+
openDeleteDialog(deleteRecId, deleteDate, !rec.cron);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Delete dialog option handlers
|
|
428
|
+
if (deleteDialog) {
|
|
429
|
+
var deleteOptions = deleteDialog.querySelectorAll(".sched-delete-option");
|
|
430
|
+
for (var i = 0; i < deleteOptions.length; i++) {
|
|
431
|
+
(function (opt) {
|
|
432
|
+
opt.addEventListener("click", function (e) {
|
|
433
|
+
e.stopPropagation();
|
|
434
|
+
var action = opt.dataset.delete;
|
|
435
|
+
if (action === "cancel") {
|
|
436
|
+
closeDeleteDialog();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
var recId = deleteDialog.dataset.recId;
|
|
440
|
+
var dateStr = deleteDialog.dataset.eventDate;
|
|
441
|
+
if (!recId) return;
|
|
442
|
+
if (action === "this") {
|
|
443
|
+
if (dateStr) {
|
|
444
|
+
var dp = dateStr.split("-");
|
|
445
|
+
var next = new Date(parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10));
|
|
446
|
+
next.setDate(next.getDate() + 1);
|
|
447
|
+
var newDate = next.getFullYear() + "-" + configCtx.pad(next.getMonth() + 1) + "-" + configCtx.pad(next.getDate());
|
|
448
|
+
configCtx.send({ type: "loop_registry_update", id: recId, data: { date: newDate } });
|
|
449
|
+
}
|
|
450
|
+
} else if (action === "following") {
|
|
451
|
+
if (dateStr) {
|
|
452
|
+
var dp2 = dateStr.split("-");
|
|
453
|
+
var prev = new Date(parseInt(dp2[0], 10), parseInt(dp2[1], 10) - 1, parseInt(dp2[2], 10));
|
|
454
|
+
prev.setDate(prev.getDate() - 1);
|
|
455
|
+
var endDate = prev.getFullYear() + "-" + configCtx.pad(prev.getMonth() + 1) + "-" + configCtx.pad(prev.getDate());
|
|
456
|
+
configCtx.send({ type: "loop_registry_update", id: recId, data: { recurrenceEnd: { type: "until", date: endDate } } });
|
|
457
|
+
}
|
|
458
|
+
} else if (action === "all") {
|
|
459
|
+
configCtx.send({ type: "loop_registry_remove", id: recId });
|
|
460
|
+
}
|
|
461
|
+
closeDeleteDialog();
|
|
462
|
+
});
|
|
463
|
+
})(deleteOptions[i]);
|
|
464
|
+
}
|
|
465
|
+
// Close on backdrop click
|
|
466
|
+
deleteDialog.addEventListener("click", function (e) {
|
|
467
|
+
if (e.target === deleteDialog) closeDeleteDialog();
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Close color palette on any click outside it
|
|
472
|
+
document.addEventListener("click", function (e) {
|
|
473
|
+
var pal = document.getElementById("sched-create-color-palette");
|
|
474
|
+
if (pal && !pal.classList.contains("hidden")) {
|
|
475
|
+
if (!pal.contains(e.target) && !e.target.closest("#sched-create-color-btn")) {
|
|
476
|
+
pal.classList.add("hidden");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Close popover on outside click
|
|
482
|
+
document.addEventListener("click", function (e) {
|
|
483
|
+
var cp = configCtx.getCreatePopover();
|
|
484
|
+
if (!cp || cp.classList.contains("hidden")) return;
|
|
485
|
+
if (cp.contains(e.target)) return;
|
|
486
|
+
// Also ignore clicks on calendar cells (they open the popover)
|
|
487
|
+
if (e.target.closest(".scheduler-cell") || e.target.closest(".scheduler-week-slot")) return;
|
|
488
|
+
closeCreateModal();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Escape key
|
|
492
|
+
document.addEventListener("keydown", function (e) {
|
|
493
|
+
var cp = configCtx.getCreatePopover();
|
|
494
|
+
if (e.key === "Escape" && cp && !cp.classList.contains("hidden")) {
|
|
495
|
+
// Close recurrence dropdown first if open
|
|
496
|
+
var dd = document.getElementById("sched-create-recurrence-dropdown");
|
|
497
|
+
if (dd && !dd.classList.contains("hidden")) {
|
|
498
|
+
dd.classList.add("hidden");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
closeCreateModal();
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function updateRecurrenceBtn() {
|
|
507
|
+
var btn = document.getElementById("sched-create-recurrence-btn");
|
|
508
|
+
if (btn) {
|
|
509
|
+
btn.classList.toggle("has-recurrence", createRecurrence !== "none");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* When today is selected, set min on the time input so past times appear disabled.
|
|
515
|
+
* If the current time value is before now, bump it to the next quarter-hour.
|
|
516
|
+
*/
|
|
517
|
+
function enforceMinTime() {
|
|
518
|
+
var timeInput = document.getElementById("sched-create-time");
|
|
519
|
+
var datePicker = document.getElementById("sched-create-date-picker");
|
|
520
|
+
if (!timeInput || !datePicker) return;
|
|
521
|
+
|
|
522
|
+
var now = new Date();
|
|
523
|
+
var todayStr = now.getFullYear() + "-" + configCtx.pad(now.getMonth() + 1) + "-" + configCtx.pad(now.getDate());
|
|
524
|
+
var isToday = datePicker.value === todayStr;
|
|
525
|
+
|
|
526
|
+
if (isToday) {
|
|
527
|
+
// Round up to the next minute for min
|
|
528
|
+
var minMinutes = now.getHours() * 60 + now.getMinutes() + 1;
|
|
529
|
+
var minH = Math.floor(minMinutes / 60);
|
|
530
|
+
var minM = minMinutes % 60;
|
|
531
|
+
if (minH >= 24) { minH = 23; minM = 59; }
|
|
532
|
+
var minVal = configCtx.pad(minH) + ":" + configCtx.pad(minM);
|
|
533
|
+
timeInput.min = minVal;
|
|
534
|
+
|
|
535
|
+
// If current value is before min, bump it
|
|
536
|
+
if (timeInput.value < minVal) {
|
|
537
|
+
timeInput.value = minVal;
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
timeInput.removeAttribute("min");
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function updateIntervalBtn() {
|
|
545
|
+
var btn = document.getElementById("sched-create-interval-btn");
|
|
546
|
+
if (btn) {
|
|
547
|
+
btn.classList.toggle("has-recurrence", createInterval !== "none");
|
|
548
|
+
}
|
|
549
|
+
// Show/hide interval end conditions section
|
|
550
|
+
var endSection = document.getElementById("sched-interval-end-section");
|
|
551
|
+
if (endSection) {
|
|
552
|
+
endSection.classList.toggle("hidden", createInterval === "none");
|
|
553
|
+
}
|
|
554
|
+
// Always show time picker (start time is needed even with interval)
|
|
555
|
+
// Update skip-if-running visibility
|
|
556
|
+
updateRecurrenceBtn();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export function getPreviewEl() {
|
|
560
|
+
return previewEl;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function removePreview() {
|
|
564
|
+
if (previewEl && previewEl.parentNode) {
|
|
565
|
+
previewEl.parentNode.removeChild(previewEl);
|
|
566
|
+
}
|
|
567
|
+
previewEl = null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function showPreviewOnCell(cell) {
|
|
571
|
+
removePreview();
|
|
572
|
+
var dragState = configCtx.getDragState();
|
|
573
|
+
var label = dragState.draggedTaskName || "(No title)";
|
|
574
|
+
var el = document.createElement("div");
|
|
575
|
+
el.className = "scheduler-event preview";
|
|
576
|
+
el.textContent = label;
|
|
577
|
+
cell.appendChild(el);
|
|
578
|
+
previewEl = el;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export function showPreviewOnSlot(slot) {
|
|
582
|
+
removePreview();
|
|
583
|
+
var dragState = configCtx.getDragState();
|
|
584
|
+
var label = dragState.draggedTaskName || "(No title)";
|
|
585
|
+
var hour = parseInt(slot.dataset.hour, 10);
|
|
586
|
+
var quarter = parseInt(slot.dataset.quarter || "0", 10);
|
|
587
|
+
var minute = quarter * 15;
|
|
588
|
+
var timeStr = configCtx.pad(hour) + ":" + configCtx.pad(minute);
|
|
589
|
+
var col = slot.closest(".scheduler-week-day-col");
|
|
590
|
+
if (!col) return;
|
|
591
|
+
var topPct = ((hour * 60 + minute) / 1440) * 100;
|
|
592
|
+
var el = document.createElement("div");
|
|
593
|
+
el.className = "scheduler-week-event preview";
|
|
594
|
+
el.style.cssText = "top:" + topPct + "%;height:calc(160vh / 48)";
|
|
595
|
+
el.textContent = timeStr + " " + label;
|
|
596
|
+
col.appendChild(el);
|
|
597
|
+
previewEl = el;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function showPreviewForCreate(anchorEl, label) {
|
|
601
|
+
removePreview();
|
|
602
|
+
if (!anchorEl) return;
|
|
603
|
+
var text = label || "(No title)";
|
|
604
|
+
if (anchorEl.classList.contains("scheduler-week-slot")) {
|
|
605
|
+
var hour = parseInt(anchorEl.dataset.hour, 10);
|
|
606
|
+
var quarter = parseInt(anchorEl.dataset.quarter || "0", 10);
|
|
607
|
+
var minute = quarter * 15;
|
|
608
|
+
var timeStr = configCtx.pad(hour) + ":" + configCtx.pad(minute);
|
|
609
|
+
var col = anchorEl.closest(".scheduler-week-day-col");
|
|
610
|
+
if (!col) return;
|
|
611
|
+
var topPct = ((hour * 60 + minute) / 1440) * 100;
|
|
612
|
+
var el = document.createElement("div");
|
|
613
|
+
el.className = "scheduler-week-event preview";
|
|
614
|
+
el.style.cssText = "top:" + topPct + "%;height:calc(160vh / 48)";
|
|
615
|
+
el.textContent = timeStr + " " + text;
|
|
616
|
+
col.appendChild(el);
|
|
617
|
+
previewEl = el;
|
|
618
|
+
} else if (anchorEl.classList.contains("scheduler-cell")) {
|
|
619
|
+
var el = document.createElement("div");
|
|
620
|
+
el.className = "scheduler-event preview";
|
|
621
|
+
el.textContent = text;
|
|
622
|
+
anchorEl.appendChild(el);
|
|
623
|
+
previewEl = el;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export function applyDraggedTask() {
|
|
628
|
+
var dragState = configCtx.getDragState();
|
|
629
|
+
if (!dragState.draggedTaskId) return;
|
|
630
|
+
var taskHidden = document.getElementById("sched-create-task");
|
|
631
|
+
var taskLabel = document.getElementById("sched-create-task-label");
|
|
632
|
+
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
633
|
+
if (taskHidden) taskHidden.value = dragState.draggedTaskId;
|
|
634
|
+
if (taskLabel) taskLabel.textContent = dragState.draggedTaskName || dragState.draggedTaskId;
|
|
635
|
+
if (taskBtn) { taskBtn.classList.add("has-value"); taskBtn.classList.remove("invalid"); }
|
|
636
|
+
// Mark the matching item as selected in the dropdown list
|
|
637
|
+
var taskListEl = document.getElementById("sched-create-task-list");
|
|
638
|
+
if (taskListEl) {
|
|
639
|
+
var items = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
640
|
+
for (var k = 0; k < items.length; k++) {
|
|
641
|
+
items[k].classList.toggle("selected", items[k].dataset.taskId === dragState.draggedTaskId);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// Auto-generate title: "taskName - HH:MM"
|
|
645
|
+
var titleInput = document.getElementById("sched-create-title");
|
|
646
|
+
var timeInput = document.getElementById("sched-create-time");
|
|
647
|
+
if (titleInput && (dragState.draggedTaskName || dragState.draggedTaskId)) {
|
|
648
|
+
var name = dragState.draggedTaskName || dragState.draggedTaskId;
|
|
649
|
+
var time = timeInput ? timeInput.value : "";
|
|
650
|
+
titleInput.value = time ? name + " - " + time : name;
|
|
651
|
+
}
|
|
652
|
+
// Update preview text to match auto-title
|
|
653
|
+
if (previewEl && titleInput) {
|
|
654
|
+
var previewText = titleInput.value || "(No title)";
|
|
655
|
+
if (previewEl.classList.contains("scheduler-week-event") && timeInput) {
|
|
656
|
+
previewText = timeInput.value + " " + (titleInput.value || "(No title)");
|
|
657
|
+
}
|
|
658
|
+
previewEl.textContent = previewText;
|
|
659
|
+
}
|
|
660
|
+
configCtx.clearDragState();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export function openCreateModalWithRecord(rec, anchorEl) {
|
|
664
|
+
var createPopover = configCtx.getCreatePopover();
|
|
665
|
+
// Parse date/time from record
|
|
666
|
+
var date = null;
|
|
667
|
+
var hour = null;
|
|
668
|
+
if (rec.date) {
|
|
669
|
+
var dp = rec.date.split("-");
|
|
670
|
+
date = new Date(parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10));
|
|
671
|
+
}
|
|
672
|
+
if (rec.time) {
|
|
673
|
+
var tp = rec.time.split(":");
|
|
674
|
+
hour = parseInt(tp[0], 10) || 0;
|
|
675
|
+
var mins = parseInt(tp[1], 10) || 0;
|
|
676
|
+
if (date) { date.setHours(hour, mins, 0); }
|
|
677
|
+
}
|
|
678
|
+
// Mark as editing existing record
|
|
679
|
+
createEditingRecId = rec.id;
|
|
680
|
+
|
|
681
|
+
// Open the create modal normally first
|
|
682
|
+
openCreateModal(date || new Date(), hour, anchorEl);
|
|
683
|
+
|
|
684
|
+
// Show delete button
|
|
685
|
+
var deleteBtn = document.getElementById("sched-create-delete");
|
|
686
|
+
if (deleteBtn) deleteBtn.classList.remove("hidden");
|
|
687
|
+
|
|
688
|
+
// Now override with record values
|
|
689
|
+
var titleInput = document.getElementById("sched-create-title");
|
|
690
|
+
if (titleInput) titleInput.value = rec.name || "";
|
|
691
|
+
|
|
692
|
+
var descInput = document.getElementById("sched-create-desc");
|
|
693
|
+
if (descInput) descInput.value = rec.description || "";
|
|
694
|
+
|
|
695
|
+
// Set color
|
|
696
|
+
if (rec.color) {
|
|
697
|
+
createColor = rec.color;
|
|
698
|
+
var colorDot = document.getElementById("sched-create-color-dot");
|
|
699
|
+
if (colorDot) colorDot.style.background = createColor;
|
|
700
|
+
var swatches = createPopover.querySelectorAll(".sched-color-swatch");
|
|
701
|
+
for (var si = 0; si < swatches.length; si++) {
|
|
702
|
+
swatches[si].classList.toggle("active", swatches[si].dataset.color === createColor);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Set skip-if-running
|
|
707
|
+
var skipRunningEl = document.getElementById("sched-skip-running");
|
|
708
|
+
if (skipRunningEl) skipRunningEl.checked = rec.skipIfRunning !== false;
|
|
709
|
+
|
|
710
|
+
// Set run mode and iterations
|
|
711
|
+
var editRunMode = (rec.maxIterations && rec.maxIterations > 1) ? "multi" : "single";
|
|
712
|
+
var editRunBtns = createPopover.querySelectorAll(".sched-run-mode-btn");
|
|
713
|
+
for (var rb = 0; rb < editRunBtns.length; rb++) {
|
|
714
|
+
editRunBtns[rb].classList.toggle("active", editRunBtns[rb].dataset.mode === editRunMode);
|
|
715
|
+
}
|
|
716
|
+
var editIterGroup = document.getElementById("sched-create-iter-group");
|
|
717
|
+
if (editIterGroup) editIterGroup.classList.toggle("hidden", editRunMode !== "multi");
|
|
718
|
+
if (rec.maxIterations && rec.maxIterations > 1) {
|
|
719
|
+
var iterInput = document.getElementById("sched-create-iterations");
|
|
720
|
+
if (iterInput) iterInput.value = rec.maxIterations;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Set linked task
|
|
724
|
+
if (rec.linkedTaskId) {
|
|
725
|
+
var taskHidden = document.getElementById("sched-create-task");
|
|
726
|
+
var taskLabel2 = document.getElementById("sched-create-task-label");
|
|
727
|
+
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
728
|
+
var taskListEl = document.getElementById("sched-create-task-list");
|
|
729
|
+
var records = configCtx.getRecords();
|
|
730
|
+
if (taskHidden) taskHidden.value = rec.linkedTaskId;
|
|
731
|
+
// Find the task name
|
|
732
|
+
var taskName = rec.linkedTaskId;
|
|
733
|
+
for (var j = 0; j < records.length; j++) {
|
|
734
|
+
if (records[j].id === rec.linkedTaskId) { taskName = records[j].name || records[j].id; break; }
|
|
735
|
+
}
|
|
736
|
+
if (taskLabel2) taskLabel2.textContent = taskName;
|
|
737
|
+
if (taskBtn) { taskBtn.classList.add("has-value"); taskBtn.classList.remove("invalid"); }
|
|
738
|
+
if (taskListEl) {
|
|
739
|
+
var items = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
740
|
+
for (var k = 0; k < items.length; k++) {
|
|
741
|
+
items[k].classList.toggle("selected", items[k].dataset.taskId === rec.linkedTaskId);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Restore interval from cron
|
|
747
|
+
if (rec.cron) {
|
|
748
|
+
var cronParts = rec.cron.trim().split(/\s+/);
|
|
749
|
+
if (cronParts.length === 5) {
|
|
750
|
+
var detectedMinInterval = null;
|
|
751
|
+
var detectedHrInterval = null;
|
|
752
|
+
// Detect minute-level interval: e.g. "0,5,10,... * * * *" or "*/5 * * * *"
|
|
753
|
+
if (cronParts[1] === "*" && cronParts[2] === "*") {
|
|
754
|
+
detectedMinInterval = configCtx.detectInterval(cronParts[0], 60);
|
|
755
|
+
}
|
|
756
|
+
// Detect hour-level interval: e.g. "0 1,3,5,... * * *"
|
|
757
|
+
if (!detectedMinInterval && cronParts[2] === "*") {
|
|
758
|
+
detectedHrInterval = configCtx.detectInterval(cronParts[1], 24);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (detectedMinInterval) {
|
|
762
|
+
createInterval = "custom";
|
|
763
|
+
createIntervalCustom = { value: detectedMinInterval, unit: "minute" };
|
|
764
|
+
var intValEl = document.getElementById("sched-interval-custom-value");
|
|
765
|
+
if (intValEl) intValEl.value = detectedMinInterval;
|
|
766
|
+
var intUnitBtns = document.querySelectorAll("#sched-interval-custom-unit .sched-interval-seg");
|
|
767
|
+
for (var iu = 0; iu < intUnitBtns.length; iu++) {
|
|
768
|
+
intUnitBtns[iu].classList.toggle("active", intUnitBtns[iu].dataset.unit === "minute");
|
|
769
|
+
}
|
|
770
|
+
updateIntervalBtn();
|
|
771
|
+
} else if (detectedHrInterval) {
|
|
772
|
+
createInterval = "custom";
|
|
773
|
+
createIntervalCustom = { value: detectedHrInterval, unit: "hour" };
|
|
774
|
+
var intValEl2 = document.getElementById("sched-interval-custom-value");
|
|
775
|
+
if (intValEl2) intValEl2.value = detectedHrInterval;
|
|
776
|
+
var intUnitBtns2 = document.querySelectorAll("#sched-interval-custom-unit .sched-interval-seg");
|
|
777
|
+
for (var iu2 = 0; iu2 < intUnitBtns2.length; iu2++) {
|
|
778
|
+
intUnitBtns2[iu2].classList.toggle("active", intUnitBtns2[iu2].dataset.unit === "hour");
|
|
779
|
+
}
|
|
780
|
+
updateIntervalBtn();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Restore recurrence from cron (if no interval detected, or combined with interval)
|
|
784
|
+
if (!detectedMinInterval && !detectedHrInterval) {
|
|
785
|
+
var cronDow = cronParts[4];
|
|
786
|
+
var cronDom = cronParts[2];
|
|
787
|
+
var cronMonth = cronParts[3];
|
|
788
|
+
if (cronDow === "*" && cronDom === "*" && cronMonth === "*") {
|
|
789
|
+
createRecurrence = "daily";
|
|
790
|
+
} else if (cronDow === "1-5" && cronDom === "*") {
|
|
791
|
+
createRecurrence = "weekdays";
|
|
792
|
+
} else if (cronDom !== "*" && cronMonth !== "*") {
|
|
793
|
+
createRecurrence = "yearly";
|
|
794
|
+
} else if (cronDom !== "*" && cronDow === "*") {
|
|
795
|
+
createRecurrence = "monthly";
|
|
796
|
+
} else if (cronDow !== "*" && cronDom === "*") {
|
|
797
|
+
// Check if it matches a single day (weekly)
|
|
798
|
+
var dowVals = cronDow.split(",");
|
|
799
|
+
if (dowVals.length === 1) {
|
|
800
|
+
createRecurrence = "weekly";
|
|
801
|
+
} else if (dowVals.length === 7) {
|
|
802
|
+
createRecurrence = "daily";
|
|
803
|
+
} else {
|
|
804
|
+
createRecurrence = "custom";
|
|
805
|
+
createCustomConfirmed = true;
|
|
806
|
+
// Set custom panel values
|
|
807
|
+
document.getElementById("sched-custom-interval").value = "1";
|
|
808
|
+
document.getElementById("sched-custom-unit").value = "week";
|
|
809
|
+
var customDowBtns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn");
|
|
810
|
+
for (var cd = 0; cd < customDowBtns.length; cd++) {
|
|
811
|
+
customDowBtns[cd].classList.toggle("active", dowVals.indexOf(customDowBtns[cd].dataset.dow) !== -1);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
updateRecurrenceBtn();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Restore interval end conditions
|
|
821
|
+
if (rec.intervalEnd) {
|
|
822
|
+
createIntervalEnd = rec.intervalEnd.type || "allday";
|
|
823
|
+
var editIendOpts = document.querySelectorAll(".sched-interval-end-opt");
|
|
824
|
+
for (var ei = 0; ei < editIendOpts.length; ei++) {
|
|
825
|
+
editIendOpts[ei].classList.toggle("active", editIendOpts[ei].dataset.iend === createIntervalEnd);
|
|
826
|
+
}
|
|
827
|
+
var editAfterRow = document.getElementById("sched-interval-end-after-row");
|
|
828
|
+
var editUntilRow = document.getElementById("sched-interval-end-until-row");
|
|
829
|
+
if (createIntervalEnd === "after") {
|
|
830
|
+
createIntervalEndAfter = rec.intervalEnd.count || 5;
|
|
831
|
+
if (editAfterRow) editAfterRow.classList.remove("hidden");
|
|
832
|
+
if (editUntilRow) editUntilRow.classList.add("hidden");
|
|
833
|
+
var editAfterInput = document.getElementById("sched-interval-end-after");
|
|
834
|
+
if (editAfterInput) editAfterInput.value = createIntervalEndAfter;
|
|
835
|
+
} else if (createIntervalEnd === "until") {
|
|
836
|
+
createIntervalEndTime = rec.intervalEnd.time || "18:00";
|
|
837
|
+
if (editAfterRow) editAfterRow.classList.add("hidden");
|
|
838
|
+
if (editUntilRow) editUntilRow.classList.remove("hidden");
|
|
839
|
+
var editTimeInput = document.getElementById("sched-interval-end-time");
|
|
840
|
+
if (editTimeInput) editTimeInput.value = createIntervalEndTime;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Update preview to show record name
|
|
845
|
+
if (previewEl) {
|
|
846
|
+
var previewText = rec.name || "(No title)";
|
|
847
|
+
if (previewEl.classList.contains("scheduler-week-event") && rec.time) {
|
|
848
|
+
previewText = rec.time + " " + previewText;
|
|
849
|
+
}
|
|
850
|
+
previewEl.textContent = previewText;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export function openCreateModal(date, hour, anchorEl) {
|
|
855
|
+
var createPopover = configCtx.getCreatePopover();
|
|
856
|
+
if (!createPopover) return;
|
|
857
|
+
// Reset editing state (openCreateModalWithRecord sets this before calling us)
|
|
858
|
+
if (!createEditingRecId) {
|
|
859
|
+
var deleteBtn = document.getElementById("sched-create-delete");
|
|
860
|
+
if (deleteBtn) deleteBtn.classList.add("hidden");
|
|
861
|
+
}
|
|
862
|
+
createSelectedDate = date || new Date();
|
|
863
|
+
createRecurrence = "none";
|
|
864
|
+
createCustomConfirmed = false;
|
|
865
|
+
createInterval = "none";
|
|
866
|
+
createIntervalCustom = null;
|
|
867
|
+
createIntervalEnd = "allday";
|
|
868
|
+
createIntervalEndAfter = 5;
|
|
869
|
+
createIntervalEndTime = "";
|
|
870
|
+
createColor = "#ffb86c";
|
|
871
|
+
|
|
872
|
+
// Reset form
|
|
873
|
+
document.getElementById("sched-create-title").value = "";
|
|
874
|
+
document.getElementById("sched-create-desc").value = "";
|
|
875
|
+
var iterReset = document.getElementById("sched-create-iterations");
|
|
876
|
+
if (iterReset) iterReset.value = "3";
|
|
877
|
+
// Reset run mode to single
|
|
878
|
+
var runModeBtns = createPopover.querySelectorAll(".sched-run-mode-btn");
|
|
879
|
+
for (var rm = 0; rm < runModeBtns.length; rm++) {
|
|
880
|
+
runModeBtns[rm].classList.toggle("active", runModeBtns[rm].dataset.mode === "single");
|
|
881
|
+
}
|
|
882
|
+
var iterGroup = document.getElementById("sched-create-iter-group");
|
|
883
|
+
if (iterGroup) iterGroup.classList.add("hidden");
|
|
884
|
+
|
|
885
|
+
// Reset color
|
|
886
|
+
var colorDot = document.getElementById("sched-create-color-dot");
|
|
887
|
+
if (colorDot) colorDot.style.background = createColor;
|
|
888
|
+
var palette = document.getElementById("sched-create-color-palette");
|
|
889
|
+
if (palette) palette.classList.add("hidden");
|
|
890
|
+
var swatches = createPopover.querySelectorAll(".sched-color-swatch");
|
|
891
|
+
for (var si = 0; si < swatches.length; si++) {
|
|
892
|
+
swatches[si].classList.toggle("active", swatches[si].dataset.color === createColor);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Populate task dropdown (only tasks -- exclude ralph and schedule)
|
|
896
|
+
var taskHidden = document.getElementById("sched-create-task");
|
|
897
|
+
var taskLabel = document.getElementById("sched-create-task-label");
|
|
898
|
+
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
899
|
+
var taskListEl = document.getElementById("sched-create-task-list");
|
|
900
|
+
var records = configCtx.getRecords();
|
|
901
|
+
if (taskHidden) taskHidden.value = "";
|
|
902
|
+
if (taskLabel) taskLabel.textContent = "Select a task";
|
|
903
|
+
if (taskBtn) { taskBtn.classList.remove("has-value"); taskBtn.classList.remove("invalid"); }
|
|
904
|
+
if (taskListEl) {
|
|
905
|
+
taskListEl.classList.add("hidden");
|
|
906
|
+
var tasks = records.filter(function (r) { return r.source !== "ralph" && r.source !== "schedule"; });
|
|
907
|
+
if (tasks.length === 0) {
|
|
908
|
+
taskListEl.innerHTML = '<div class="sched-create-task-empty">No tasks available</div>';
|
|
909
|
+
} else {
|
|
910
|
+
var html = "";
|
|
911
|
+
for (var i = 0; i < tasks.length; i++) {
|
|
912
|
+
html += '<div class="sched-create-task-item" data-task-id="' + configCtx.esc(tasks[i].id) + '">' + configCtx.esc(tasks[i].name || tasks[i].id) + '</div>';
|
|
913
|
+
}
|
|
914
|
+
taskListEl.innerHTML = html;
|
|
915
|
+
// Bind click handlers
|
|
916
|
+
var items = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
917
|
+
for (var j = 0; j < items.length; j++) {
|
|
918
|
+
(function (item) {
|
|
919
|
+
item.addEventListener("click", function (e) {
|
|
920
|
+
e.stopPropagation();
|
|
921
|
+
var id = item.dataset.taskId;
|
|
922
|
+
var name = item.textContent;
|
|
923
|
+
if (taskHidden) taskHidden.value = id;
|
|
924
|
+
if (taskLabel) taskLabel.textContent = name;
|
|
925
|
+
if (taskBtn) { taskBtn.classList.add("has-value"); taskBtn.classList.remove("invalid"); }
|
|
926
|
+
// Update selected state
|
|
927
|
+
var all = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
928
|
+
for (var k = 0; k < all.length; k++) {
|
|
929
|
+
all[k].classList.toggle("selected", all[k] === item);
|
|
930
|
+
}
|
|
931
|
+
taskListEl.classList.add("hidden");
|
|
932
|
+
});
|
|
933
|
+
})(items[j]);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Set date picker
|
|
939
|
+
var dateStr = createSelectedDate.getFullYear() + "-" + configCtx.pad(createSelectedDate.getMonth() + 1) + "-" + configCtx.pad(createSelectedDate.getDate());
|
|
940
|
+
document.getElementById("sched-create-date").value = dateStr;
|
|
941
|
+
var datePicker = document.getElementById("sched-create-date-picker");
|
|
942
|
+
if (datePicker) {
|
|
943
|
+
datePicker.value = dateStr;
|
|
944
|
+
var todayNow = new Date();
|
|
945
|
+
var todayMin = todayNow.getFullYear() + "-" + configCtx.pad(todayNow.getMonth() + 1) + "-" + configCtx.pad(todayNow.getDate());
|
|
946
|
+
datePicker.min = todayMin;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Time (use minutes from createSelectedDate for 15-min snapping)
|
|
950
|
+
if (hour !== null && hour !== undefined) {
|
|
951
|
+
var mins = createSelectedDate.getMinutes ? createSelectedDate.getMinutes() : 0;
|
|
952
|
+
document.getElementById("sched-create-time").value = configCtx.pad(hour) + ":" + configCtx.pad(mins);
|
|
953
|
+
} else {
|
|
954
|
+
// Default to current time (next quarter-hour)
|
|
955
|
+
var nowT = new Date();
|
|
956
|
+
var nowMins = nowT.getHours() * 60 + nowT.getMinutes();
|
|
957
|
+
var nextQ = Math.ceil(nowMins / 15) * 15;
|
|
958
|
+
var defH = Math.floor(nextQ / 60);
|
|
959
|
+
var defM = nextQ % 60;
|
|
960
|
+
if (defH >= 24) { defH = 23; defM = 45; }
|
|
961
|
+
document.getElementById("sched-create-time").value = configCtx.pad(defH) + ":" + configCtx.pad(defM);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Update recurrence labels
|
|
965
|
+
updateRecurrenceLabels(createSelectedDate);
|
|
966
|
+
|
|
967
|
+
// Enforce min time for today
|
|
968
|
+
enforceMinTime();
|
|
969
|
+
|
|
970
|
+
// Reset recurrence
|
|
971
|
+
createRecurrence = "none";
|
|
972
|
+
createCustomConfirmed = false;
|
|
973
|
+
updateRecurrenceBtn();
|
|
974
|
+
|
|
975
|
+
// Reset interval
|
|
976
|
+
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
977
|
+
var timeInput = document.getElementById("sched-create-time");
|
|
978
|
+
if (timeInput) timeInput.style.display = "";
|
|
979
|
+
|
|
980
|
+
// Reset interval end conditions
|
|
981
|
+
var iendOpts = document.querySelectorAll(".sched-interval-end-opt");
|
|
982
|
+
for (var ie = 0; ie < iendOpts.length; ie++) {
|
|
983
|
+
iendOpts[ie].classList.toggle("active", iendOpts[ie].dataset.iend === "allday");
|
|
984
|
+
}
|
|
985
|
+
var iendAfterRow = document.getElementById("sched-interval-end-after-row");
|
|
986
|
+
if (iendAfterRow) iendAfterRow.classList.add("hidden");
|
|
987
|
+
var iendUntilRow = document.getElementById("sched-interval-end-until-row");
|
|
988
|
+
if (iendUntilRow) iendUntilRow.classList.add("hidden");
|
|
989
|
+
var iendAfterInput = document.getElementById("sched-interval-end-after");
|
|
990
|
+
if (iendAfterInput) iendAfterInput.value = "5";
|
|
991
|
+
var iendTimeInput = document.getElementById("sched-interval-end-time");
|
|
992
|
+
if (iendTimeInput) iendTimeInput.value = "18:00";
|
|
993
|
+
|
|
994
|
+
updateIntervalBtn();
|
|
995
|
+
|
|
996
|
+
// Reset custom panel
|
|
997
|
+
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
998
|
+
document.getElementById("sched-custom-interval").value = "1";
|
|
999
|
+
document.getElementById("sched-custom-unit").value = "week";
|
|
1000
|
+
document.getElementById("sched-custom-dow-section").style.display = "";
|
|
1001
|
+
var customDowBtns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn");
|
|
1002
|
+
for (var i = 0; i < customDowBtns.length; i++) {
|
|
1003
|
+
customDowBtns[i].classList.toggle("active", parseInt(customDowBtns[i].dataset.dow) === createSelectedDate.getDay());
|
|
1004
|
+
}
|
|
1005
|
+
document.getElementById("sched-custom-end").value = "never";
|
|
1006
|
+
document.getElementById("sched-custom-end-label").textContent = "Never";
|
|
1007
|
+
var endItems = document.querySelectorAll(".sched-custom-end-item");
|
|
1008
|
+
for (var ei = 0; ei < endItems.length; ei++) {
|
|
1009
|
+
endItems[ei].classList.toggle("active", endItems[ei].dataset.value === "never");
|
|
1010
|
+
}
|
|
1011
|
+
document.getElementById("sched-custom-end-list").classList.add("hidden");
|
|
1012
|
+
createEndType = "never";
|
|
1013
|
+
createEndDate = null;
|
|
1014
|
+
createEndAfter = 10;
|
|
1015
|
+
document.getElementById("sched-custom-end-date-btn").classList.add("hidden");
|
|
1016
|
+
document.getElementById("sched-custom-end-after-wrap").classList.add("hidden");
|
|
1017
|
+
document.getElementById("sched-custom-end-calendar").classList.add("hidden");
|
|
1018
|
+
|
|
1019
|
+
// Show preview event on the calendar cell
|
|
1020
|
+
var dragState = configCtx.getDragState();
|
|
1021
|
+
showPreviewForCreate(anchorEl, dragState.draggedTaskName || null);
|
|
1022
|
+
|
|
1023
|
+
// Position near anchor cell
|
|
1024
|
+
createPopover.classList.remove("hidden");
|
|
1025
|
+
positionCreatePopover(anchorEl);
|
|
1026
|
+
|
|
1027
|
+
try { lucide.createIcons({ node: createPopover }); } catch (e) {}
|
|
1028
|
+
setTimeout(function () { document.getElementById("sched-create-title").focus(); }, 50);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function positionCreatePopover(anchorEl) {
|
|
1032
|
+
var createPopover = configCtx.getCreatePopover();
|
|
1033
|
+
var contentCalEl = configCtx.getContentCalEl();
|
|
1034
|
+
if (!createPopover || !anchorEl) {
|
|
1035
|
+
// Fallback: center in scheduler content area
|
|
1036
|
+
if (createPopover && contentCalEl) {
|
|
1037
|
+
var cRect = contentCalEl.getBoundingClientRect();
|
|
1038
|
+
createPopover.style.left = (cRect.left + cRect.width / 2 - 180) + "px";
|
|
1039
|
+
createPopover.style.top = (cRect.top + 60) + "px";
|
|
1040
|
+
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
var rect = anchorEl.getBoundingClientRect();
|
|
1045
|
+
var popW = 360;
|
|
1046
|
+
var popH = createPopover.offsetHeight || 300;
|
|
1047
|
+
|
|
1048
|
+
// Try to place to the right of the cell
|
|
1049
|
+
var left = rect.right + 8;
|
|
1050
|
+
var top = rect.top;
|
|
1051
|
+
|
|
1052
|
+
// If it overflows right, place to the left
|
|
1053
|
+
if (left + popW > window.innerWidth - 10) {
|
|
1054
|
+
left = rect.left - popW - 8;
|
|
1055
|
+
}
|
|
1056
|
+
// If it still overflows left, center horizontally on the cell
|
|
1057
|
+
if (left < 10) {
|
|
1058
|
+
left = Math.max(10, rect.left + rect.width / 2 - popW / 2);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Vertical: don't overflow bottom
|
|
1062
|
+
if (top + popH > window.innerHeight - 10) {
|
|
1063
|
+
top = window.innerHeight - popH - 10;
|
|
1064
|
+
}
|
|
1065
|
+
if (top < 10) top = 10;
|
|
1066
|
+
|
|
1067
|
+
createPopover.style.left = left + "px";
|
|
1068
|
+
createPopover.style.top = top + "px";
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function updateRecurrenceLabels(date) {
|
|
1072
|
+
var dow = date.getDay();
|
|
1073
|
+
var dayName = DAY_NAMES[dow];
|
|
1074
|
+
var dom = date.getDate();
|
|
1075
|
+
var monthName = MONTH_NAMES[date.getMonth()];
|
|
1076
|
+
|
|
1077
|
+
// Weekly on {day}
|
|
1078
|
+
var weeklyEl = document.getElementById("sched-recurrence-weekly");
|
|
1079
|
+
if (weeklyEl) weeklyEl.textContent = "Weekly on " + dayName;
|
|
1080
|
+
|
|
1081
|
+
// Every second {day} of the month
|
|
1082
|
+
var weekOfMonth = Math.ceil(dom / 7);
|
|
1083
|
+
var ordinals = ["", "first", "second", "third", "fourth", "fifth"];
|
|
1084
|
+
var biweeklyEl = document.getElementById("sched-recurrence-biweekly");
|
|
1085
|
+
if (biweeklyEl) {
|
|
1086
|
+
var ordStr = ordinals[weekOfMonth] || weekOfMonth + "th";
|
|
1087
|
+
biweeklyEl.textContent = "Every " + ordStr + " " + dayName + " of the mo...";
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Every year on {month} {date}
|
|
1091
|
+
var yearlyEl = document.getElementById("sched-recurrence-yearly");
|
|
1092
|
+
if (yearlyEl) yearlyEl.textContent = "Every year on " + monthName + " " + dom;
|
|
1093
|
+
|
|
1094
|
+
// Every month on the {date}th
|
|
1095
|
+
var monthlyEl = document.getElementById("sched-recurrence-monthly");
|
|
1096
|
+
if (monthlyEl) {
|
|
1097
|
+
var suffix = "th";
|
|
1098
|
+
if (dom === 1 || dom === 21 || dom === 31) suffix = "st";
|
|
1099
|
+
else if (dom === 2 || dom === 22) suffix = "nd";
|
|
1100
|
+
else if (dom === 3 || dom === 23) suffix = "rd";
|
|
1101
|
+
monthlyEl.textContent = "Every month on the " + dom + suffix;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
export function closeCreateModal() {
|
|
1106
|
+
var createPopover = configCtx.getCreatePopover();
|
|
1107
|
+
if (createPopover) createPopover.classList.add("hidden");
|
|
1108
|
+
var dd = document.getElementById("sched-create-recurrence-dropdown");
|
|
1109
|
+
if (dd) dd.classList.add("hidden");
|
|
1110
|
+
var pal = document.getElementById("sched-create-color-palette");
|
|
1111
|
+
if (pal) pal.classList.add("hidden");
|
|
1112
|
+
var tl = document.getElementById("sched-create-task-list");
|
|
1113
|
+
if (tl) tl.classList.add("hidden");
|
|
1114
|
+
removePreview();
|
|
1115
|
+
createSelectedDate = null;
|
|
1116
|
+
createEditingRecId = null;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function openDeleteDialog(recId, eventDate, isOneOff) {
|
|
1120
|
+
var dialog = document.getElementById("sched-delete-dialog");
|
|
1121
|
+
if (!dialog) return;
|
|
1122
|
+
dialog.dataset.recId = recId;
|
|
1123
|
+
if (eventDate) {
|
|
1124
|
+
dialog.dataset.eventDate = eventDate.getFullYear() + "-" + configCtx.pad(eventDate.getMonth() + 1) + "-" + configCtx.pad(eventDate.getDate());
|
|
1125
|
+
} else {
|
|
1126
|
+
dialog.dataset.eventDate = "";
|
|
1127
|
+
}
|
|
1128
|
+
// Toggle between one-off and recurring UI
|
|
1129
|
+
var title = dialog.querySelector(".sched-delete-dialog-title");
|
|
1130
|
+
var body = dialog.querySelector(".sched-delete-dialog-body");
|
|
1131
|
+
var footer = dialog.querySelector(".sched-delete-dialog-footer");
|
|
1132
|
+
var cancelBtn = dialog.querySelector('[data-delete="cancel"]');
|
|
1133
|
+
dialog.dataset.oneOff = isOneOff ? "1" : "";
|
|
1134
|
+
if (isOneOff) {
|
|
1135
|
+
if (title) title.textContent = "Delete this event?";
|
|
1136
|
+
if (body) body.classList.add("hidden");
|
|
1137
|
+
if (cancelBtn) cancelBtn.textContent = "Cancel";
|
|
1138
|
+
// Add a "Delete" button next to cancel in footer
|
|
1139
|
+
var existingDel = footer ? footer.querySelector(".sched-delete-confirm-btn") : null;
|
|
1140
|
+
if (!existingDel && footer) {
|
|
1141
|
+
var delBtn = document.createElement("button");
|
|
1142
|
+
delBtn.className = "sched-delete-option danger sched-delete-confirm-btn";
|
|
1143
|
+
delBtn.dataset.delete = "all";
|
|
1144
|
+
delBtn.textContent = "Delete";
|
|
1145
|
+
footer.appendChild(delBtn);
|
|
1146
|
+
delBtn.addEventListener("click", function (e) {
|
|
1147
|
+
e.stopPropagation();
|
|
1148
|
+
var rid = dialog.dataset.recId;
|
|
1149
|
+
if (rid) configCtx.send({ type: "loop_registry_remove", id: rid });
|
|
1150
|
+
closeDeleteDialog();
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
if (existingDel) existingDel.classList.remove("hidden");
|
|
1154
|
+
} else {
|
|
1155
|
+
if (title) title.textContent = "Delete recurring event";
|
|
1156
|
+
if (body) body.classList.remove("hidden");
|
|
1157
|
+
if (cancelBtn) cancelBtn.textContent = "Cancel";
|
|
1158
|
+
var existingDel = footer ? footer.querySelector(".sched-delete-confirm-btn") : null;
|
|
1159
|
+
if (existingDel) existingDel.classList.add("hidden");
|
|
1160
|
+
}
|
|
1161
|
+
dialog.classList.remove("hidden");
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function closeDeleteDialog() {
|
|
1165
|
+
var dialog = document.getElementById("sched-delete-dialog");
|
|
1166
|
+
if (dialog) {
|
|
1167
|
+
dialog.classList.add("hidden");
|
|
1168
|
+
dialog.dataset.recId = "";
|
|
1169
|
+
dialog.dataset.eventDate = "";
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Build an explicit list of values offset from a start value with a given step, wrapping at max
|
|
1174
|
+
function buildOffsetList(start, step, max) {
|
|
1175
|
+
var vals = [];
|
|
1176
|
+
var v = start % max;
|
|
1177
|
+
for (var i = 0; i < max; i += step) {
|
|
1178
|
+
vals.push(v);
|
|
1179
|
+
v = (v + step) % max;
|
|
1180
|
+
}
|
|
1181
|
+
vals.sort(function (a, b) { return a - b; });
|
|
1182
|
+
return vals.join(",");
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function buildCreateCron() {
|
|
1186
|
+
if (!createSelectedDate) return null;
|
|
1187
|
+
|
|
1188
|
+
var timeVal = document.getElementById("sched-create-time").value || "09:00";
|
|
1189
|
+
var timeParts = timeVal.split(":");
|
|
1190
|
+
var h = parseInt(timeParts[0], 10);
|
|
1191
|
+
var m = parseInt(timeParts[1], 10);
|
|
1192
|
+
|
|
1193
|
+
var dow = createSelectedDate.getDay();
|
|
1194
|
+
var dom = createSelectedDate.getDate();
|
|
1195
|
+
var month = createSelectedDate.getMonth() + 1;
|
|
1196
|
+
|
|
1197
|
+
// Determine interval minutes
|
|
1198
|
+
var intervalMins = 0;
|
|
1199
|
+
if (createInterval !== "none") {
|
|
1200
|
+
if (createInterval === "custom" && createIntervalCustom) {
|
|
1201
|
+
intervalMins = createIntervalCustom.unit === "hour"
|
|
1202
|
+
? createIntervalCustom.value * 60
|
|
1203
|
+
: createIntervalCustom.value;
|
|
1204
|
+
} else {
|
|
1205
|
+
intervalMins = parseInt(createInterval, 10) || 0;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Interval only (no recurrence) = interval every day
|
|
1210
|
+
if (intervalMins > 0 && createRecurrence === "none") {
|
|
1211
|
+
if (intervalMins < 60) return buildOffsetList(m, intervalMins, 60) + " * * * *";
|
|
1212
|
+
var intHrs = Math.floor(intervalMins / 60);
|
|
1213
|
+
return String(m) + " " + buildOffsetList(h, intHrs, 24) + " * * *";
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (createRecurrence === "none" && intervalMins === 0) return null;
|
|
1217
|
+
|
|
1218
|
+
// Build minute/hour fields from interval or time
|
|
1219
|
+
var minField = String(m);
|
|
1220
|
+
var hourField = String(h);
|
|
1221
|
+
if (intervalMins > 0 && intervalMins < 60) {
|
|
1222
|
+
minField = buildOffsetList(m, intervalMins, 60);
|
|
1223
|
+
hourField = "*";
|
|
1224
|
+
} else if (intervalMins >= 60) {
|
|
1225
|
+
var intHrs2 = Math.floor(intervalMins / 60);
|
|
1226
|
+
minField = String(m);
|
|
1227
|
+
hourField = buildOffsetList(h, intHrs2, 24);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (createRecurrence === "daily") return minField + " " + hourField + " * * *";
|
|
1231
|
+
if (createRecurrence === "weekly") return minField + " " + hourField + " * * " + dow;
|
|
1232
|
+
if (createRecurrence === "biweekly") {
|
|
1233
|
+
var weekNum = Math.ceil(dom / 7);
|
|
1234
|
+
return minField + " " + hourField + " " + ((weekNum - 1) * 7 + 1) + "-" + (weekNum * 7) + " * " + dow;
|
|
1235
|
+
}
|
|
1236
|
+
if (createRecurrence === "yearly") return minField + " " + hourField + " " + dom + " " + month + " *";
|
|
1237
|
+
if (createRecurrence === "monthly") return minField + " " + hourField + " " + dom + " * *";
|
|
1238
|
+
if (createRecurrence === "weekdays") return minField + " " + hourField + " * * 1-5";
|
|
1239
|
+
|
|
1240
|
+
if (createRecurrence === "custom" && createCustomConfirmed) {
|
|
1241
|
+
return buildCustomCron(h, m);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function updateEndDateLabel() {
|
|
1248
|
+
var label = document.getElementById("sched-custom-end-date-label");
|
|
1249
|
+
if (!label || !createEndDate) return;
|
|
1250
|
+
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
1251
|
+
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1252
|
+
label.textContent = days[createEndDate.getDay()] + ", " + months[createEndDate.getMonth()] + " " + createEndDate.getDate();
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function renderEndCalendar() {
|
|
1256
|
+
var grid = document.getElementById("sched-cal-grid");
|
|
1257
|
+
var titleEl = document.getElementById("sched-cal-title");
|
|
1258
|
+
if (!grid || !createEndCalMonth) return;
|
|
1259
|
+
|
|
1260
|
+
var year = createEndCalMonth.getFullYear();
|
|
1261
|
+
var month = createEndCalMonth.getMonth();
|
|
1262
|
+
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
1263
|
+
titleEl.textContent = months[month] + " " + year;
|
|
1264
|
+
|
|
1265
|
+
var firstDay = new Date(year, month, 1).getDay();
|
|
1266
|
+
var daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
1267
|
+
var prevDays = new Date(year, month, 0).getDate();
|
|
1268
|
+
|
|
1269
|
+
var today = new Date();
|
|
1270
|
+
today.setHours(0, 0, 0, 0);
|
|
1271
|
+
|
|
1272
|
+
grid.innerHTML = "";
|
|
1273
|
+
|
|
1274
|
+
// Previous month filler
|
|
1275
|
+
for (var p = firstDay - 1; p >= 0; p--) {
|
|
1276
|
+
var d = prevDays - p;
|
|
1277
|
+
var btn = document.createElement("button");
|
|
1278
|
+
btn.className = "sched-cal-day other-month";
|
|
1279
|
+
btn.textContent = d;
|
|
1280
|
+
btn.type = "button";
|
|
1281
|
+
var prevDate = new Date(year, month - 1, d);
|
|
1282
|
+
(function (dt) {
|
|
1283
|
+
btn.addEventListener("click", function (e) {
|
|
1284
|
+
e.stopPropagation();
|
|
1285
|
+
createEndDate = dt;
|
|
1286
|
+
updateEndDateLabel();
|
|
1287
|
+
renderEndCalendar();
|
|
1288
|
+
});
|
|
1289
|
+
})(prevDate);
|
|
1290
|
+
grid.appendChild(btn);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Current month
|
|
1294
|
+
for (var i = 1; i <= daysInMonth; i++) {
|
|
1295
|
+
var btn = document.createElement("button");
|
|
1296
|
+
btn.className = "sched-cal-day";
|
|
1297
|
+
btn.textContent = i;
|
|
1298
|
+
btn.type = "button";
|
|
1299
|
+
var cellDate = new Date(year, month, i);
|
|
1300
|
+
if (cellDate.getTime() === today.getTime()) btn.classList.add("today");
|
|
1301
|
+
if (createEndDate && cellDate.getFullYear() === createEndDate.getFullYear() && cellDate.getMonth() === createEndDate.getMonth() && cellDate.getDate() === createEndDate.getDate()) {
|
|
1302
|
+
btn.classList.add("selected");
|
|
1303
|
+
}
|
|
1304
|
+
(function (dt) {
|
|
1305
|
+
btn.addEventListener("click", function (e) {
|
|
1306
|
+
e.stopPropagation();
|
|
1307
|
+
createEndDate = dt;
|
|
1308
|
+
updateEndDateLabel();
|
|
1309
|
+
renderEndCalendar();
|
|
1310
|
+
});
|
|
1311
|
+
})(cellDate);
|
|
1312
|
+
grid.appendChild(btn);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Next month filler
|
|
1316
|
+
var totalCells = firstDay + daysInMonth;
|
|
1317
|
+
var remaining = (7 - (totalCells % 7)) % 7;
|
|
1318
|
+
for (var n = 1; n <= remaining; n++) {
|
|
1319
|
+
var btn = document.createElement("button");
|
|
1320
|
+
btn.className = "sched-cal-day other-month";
|
|
1321
|
+
btn.textContent = n;
|
|
1322
|
+
btn.type = "button";
|
|
1323
|
+
var nextDate = new Date(year, month + 1, n);
|
|
1324
|
+
(function (dt) {
|
|
1325
|
+
btn.addEventListener("click", function (e) {
|
|
1326
|
+
e.stopPropagation();
|
|
1327
|
+
createEndDate = dt;
|
|
1328
|
+
updateEndDateLabel();
|
|
1329
|
+
renderEndCalendar();
|
|
1330
|
+
});
|
|
1331
|
+
})(nextDate);
|
|
1332
|
+
grid.appendChild(btn);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function buildCustomCron(h, m) {
|
|
1337
|
+
var interval = parseInt(document.getElementById("sched-custom-interval").value, 10) || 1;
|
|
1338
|
+
var unit = document.getElementById("sched-custom-unit").value;
|
|
1339
|
+
|
|
1340
|
+
if (unit === "minute") {
|
|
1341
|
+
return interval === 1 ? "*/1 * * * *" : buildOffsetList(m, interval, 60) + " * * * *";
|
|
1342
|
+
}
|
|
1343
|
+
if (unit === "hour") {
|
|
1344
|
+
return interval === 1 ? m + " */1 * * *" : m + " " + buildOffsetList(h, interval, 24) + " * * *";
|
|
1345
|
+
}
|
|
1346
|
+
if (unit === "day") {
|
|
1347
|
+
if (interval === 1) return m + " " + h + " * * *";
|
|
1348
|
+
return m + " " + h + " */" + interval + " * *";
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (unit === "week") {
|
|
1352
|
+
var days = [];
|
|
1353
|
+
var btns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn.active");
|
|
1354
|
+
for (var i = 0; i < btns.length; i++) days.push(btns[i].dataset.dow);
|
|
1355
|
+
if (days.length === 0) days.push(String(createSelectedDate ? createSelectedDate.getDay() : 0));
|
|
1356
|
+
return m + " " + h + " * * " + days.sort().join(",");
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (unit === "month") {
|
|
1360
|
+
var dom = createSelectedDate ? createSelectedDate.getDate() : 1;
|
|
1361
|
+
if (interval === 1) return m + " " + h + " " + dom + " * *";
|
|
1362
|
+
return m + " " + h + " " + dom + " */" + interval + " *";
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (unit === "year") {
|
|
1366
|
+
var dom = createSelectedDate ? createSelectedDate.getDate() : 1;
|
|
1367
|
+
var month = createSelectedDate ? createSelectedDate.getMonth() + 1 : 1;
|
|
1368
|
+
return m + " " + h + " " + dom + " " + month + " *";
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function submitCreateSchedule() {
|
|
1375
|
+
var name = document.getElementById("sched-create-title").value.trim();
|
|
1376
|
+
if (!name) { document.getElementById("sched-create-title").focus(); return; }
|
|
1377
|
+
|
|
1378
|
+
var taskId = document.getElementById("sched-create-task").value || null;
|
|
1379
|
+
if (!taskId) {
|
|
1380
|
+
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
1381
|
+
if (taskBtn) taskBtn.classList.add("invalid");
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
var ctx = configCtx.ctx;
|
|
1386
|
+
ctx.requireClayRalph(function () {
|
|
1387
|
+
var createPopover = configCtx.getCreatePopover();
|
|
1388
|
+
var description = document.getElementById("sched-create-desc").value.trim();
|
|
1389
|
+
var datePicker = document.getElementById("sched-create-date-picker");
|
|
1390
|
+
var dateVal = datePicker ? datePicker.value : document.getElementById("sched-create-date").value;
|
|
1391
|
+
var timeVal = document.getElementById("sched-create-time").value || "09:00";
|
|
1392
|
+
|
|
1393
|
+
// Reject scheduling in the past
|
|
1394
|
+
if (dateVal && timeVal) {
|
|
1395
|
+
var dp = dateVal.split("-");
|
|
1396
|
+
var tp = timeVal.split(":");
|
|
1397
|
+
if (dp.length === 3 && tp.length >= 2) {
|
|
1398
|
+
var schedDate = new Date(
|
|
1399
|
+
parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10),
|
|
1400
|
+
parseInt(tp[0], 10), parseInt(tp[1], 10), 0
|
|
1401
|
+
);
|
|
1402
|
+
if (schedDate < new Date()) {
|
|
1403
|
+
showToast("Cannot schedule a task in the past", "error");
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
var cron = buildCreateCron();
|
|
1410
|
+
|
|
1411
|
+
// Build recurrence end info
|
|
1412
|
+
var recurrenceEnd = null;
|
|
1413
|
+
// Interval-only (no recurrence): limit to the scheduled date only
|
|
1414
|
+
if (cron && createRecurrence === "none" && createInterval !== "none" && dateVal) {
|
|
1415
|
+
recurrenceEnd = { type: "until", date: dateVal };
|
|
1416
|
+
}
|
|
1417
|
+
if (cron && createRecurrence === "custom" && createCustomConfirmed) {
|
|
1418
|
+
if (createEndType === "until" && createEndDate) {
|
|
1419
|
+
var ey = createEndDate.getFullYear();
|
|
1420
|
+
var em = String(createEndDate.getMonth() + 1).padStart(2, "0");
|
|
1421
|
+
var ed = String(createEndDate.getDate()).padStart(2, "0");
|
|
1422
|
+
recurrenceEnd = { type: "until", date: ey + "-" + em + "-" + ed };
|
|
1423
|
+
} else if (createEndType === "after" && createEndAfter > 0) {
|
|
1424
|
+
recurrenceEnd = { type: "after", count: createEndAfter };
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// Build interval end info
|
|
1429
|
+
var intervalEnd = null;
|
|
1430
|
+
if (createInterval !== "none") {
|
|
1431
|
+
if (createIntervalEnd === "after" && createIntervalEndAfter > 0) {
|
|
1432
|
+
intervalEnd = { type: "after", count: createIntervalEndAfter };
|
|
1433
|
+
} else if (createIntervalEnd === "until" && createIntervalEndTime) {
|
|
1434
|
+
intervalEnd = { type: "until", time: createIntervalEndTime };
|
|
1435
|
+
}
|
|
1436
|
+
// "allday" = null (no limit)
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
var skipRunningEl = document.getElementById("sched-skip-running");
|
|
1440
|
+
var skipIfRunning = skipRunningEl ? skipRunningEl.checked : true;
|
|
1441
|
+
|
|
1442
|
+
var activeRunMode = createPopover.querySelector(".sched-run-mode-btn.active");
|
|
1443
|
+
var runMode = activeRunMode ? activeRunMode.dataset.mode : "single";
|
|
1444
|
+
var maxIterations = 1;
|
|
1445
|
+
if (runMode === "multi") {
|
|
1446
|
+
var iterInput = document.getElementById("sched-create-iterations");
|
|
1447
|
+
maxIterations = iterInput ? (parseInt(iterInput.value, 10) || 3) : 3;
|
|
1448
|
+
if (maxIterations < 2) maxIterations = 2;
|
|
1449
|
+
if (maxIterations > 100) maxIterations = 100;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
if (createEditingRecId) {
|
|
1453
|
+
configCtx.send({
|
|
1454
|
+
type: "loop_registry_update",
|
|
1455
|
+
id: createEditingRecId,
|
|
1456
|
+
data: {
|
|
1457
|
+
name: name,
|
|
1458
|
+
description: description,
|
|
1459
|
+
date: dateVal,
|
|
1460
|
+
time: timeVal,
|
|
1461
|
+
allDay: false,
|
|
1462
|
+
cron: cron,
|
|
1463
|
+
enabled: cron ? true : false,
|
|
1464
|
+
color: createColor,
|
|
1465
|
+
recurrenceEnd: recurrenceEnd,
|
|
1466
|
+
intervalEnd: intervalEnd,
|
|
1467
|
+
maxIterations: maxIterations,
|
|
1468
|
+
skipIfRunning: skipIfRunning,
|
|
1469
|
+
},
|
|
1470
|
+
});
|
|
1471
|
+
} else {
|
|
1472
|
+
configCtx.send({
|
|
1473
|
+
type: "schedule_create",
|
|
1474
|
+
data: {
|
|
1475
|
+
name: name,
|
|
1476
|
+
taskId: taskId,
|
|
1477
|
+
description: description,
|
|
1478
|
+
date: dateVal,
|
|
1479
|
+
time: timeVal,
|
|
1480
|
+
allDay: false,
|
|
1481
|
+
cron: cron,
|
|
1482
|
+
enabled: cron ? true : false,
|
|
1483
|
+
color: createColor,
|
|
1484
|
+
recurrenceEnd: recurrenceEnd,
|
|
1485
|
+
intervalEnd: intervalEnd,
|
|
1486
|
+
maxIterations: maxIterations,
|
|
1487
|
+
skipIfRunning: skipIfRunning,
|
|
1488
|
+
},
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
closeCreateModal();
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// --- Cron parser (client-side) ---
|
|
1497
|
+
|
|
1498
|
+
export function parseCronSimple(expr) {
|
|
1499
|
+
if (!expr) return null;
|
|
1500
|
+
var fields = expr.trim().split(/\s+/);
|
|
1501
|
+
if (fields.length !== 5) return null;
|
|
1502
|
+
return {
|
|
1503
|
+
minutes: parseField(fields[0], 0, 59),
|
|
1504
|
+
hours: parseField(fields[1], 0, 23),
|
|
1505
|
+
daysOfMonth: parseField(fields[2], 1, 31),
|
|
1506
|
+
months: parseField(fields[3], 1, 12),
|
|
1507
|
+
daysOfWeek: parseField(fields[4], 0, 6),
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function parseField(field, min, max) {
|
|
1512
|
+
var values = [];
|
|
1513
|
+
var parts = field.split(",");
|
|
1514
|
+
for (var i = 0; i < parts.length; i++) {
|
|
1515
|
+
var part = parts[i].trim();
|
|
1516
|
+
if (part.indexOf("/") !== -1) {
|
|
1517
|
+
var sp = part.split("/");
|
|
1518
|
+
var step = parseInt(sp[1], 10);
|
|
1519
|
+
var rMin = min, rMax = max;
|
|
1520
|
+
if (sp[0] !== "*") { var rp = sp[0].split("-"); rMin = parseInt(rp[0], 10); rMax = rp.length > 1 ? parseInt(rp[1], 10) : rMin; }
|
|
1521
|
+
for (var v = rMin; v <= rMax; v += step) values.push(v);
|
|
1522
|
+
} else if (part === "*") {
|
|
1523
|
+
for (var v = min; v <= max; v++) values.push(v);
|
|
1524
|
+
} else if (part.indexOf("-") !== -1) {
|
|
1525
|
+
var rp = part.split("-");
|
|
1526
|
+
for (var v = parseInt(rp[0], 10); v <= parseInt(rp[1], 10); v++) values.push(v);
|
|
1527
|
+
} else {
|
|
1528
|
+
values.push(parseInt(part, 10));
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return values;
|
|
1532
|
+
}
|