clay-server 2.27.0-beta.9 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +13 -126
- 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/project-connection.js +2 -0
- package/lib/project-http.js +4 -2
- package/lib/project-loop.js +110 -48
- package/lib/project-mate-interaction.js +4 -0
- package/lib/project-notifications.js +210 -0
- package/lib/project-sessions.js +5 -2
- package/lib/project-user-message.js +2 -1
- package/lib/project.js +26 -2
- package/lib/public/app.js +1193 -8517
- package/lib/public/css/command-palette.css +14 -0
- package/lib/public/css/loop.css +301 -0
- package/lib/public/css/notifications-center.css +190 -0
- package/lib/public/css/rewind.css +6 -0
- package/lib/public/index.html +89 -35
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +389 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +589 -0
- package/lib/public/modules/app-loop-wizard.js +439 -0
- package/lib/public/modules/app-messages.js +1560 -0
- package/lib/public/modules/app-misc.js +299 -0
- package/lib/public/modules/app-notifications.js +372 -0
- package/lib/public/modules/app-panels.js +888 -0
- package/lib/public/modules/app-projects.js +798 -0
- package/lib/public/modules/app-rate-limit.js +451 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +234 -0
- package/lib/public/modules/command-palette.js +27 -4
- package/lib/public/modules/input.js +31 -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/public/modules/session-search.js +13 -1
- package/lib/public/modules/sidebar-mates.js +812 -0
- package/lib/public/modules/sidebar-mobile.js +1269 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/public/modules/store.js +27 -0
- package/lib/public/modules/ws-ref.js +7 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +96 -717
- package/lib/sdk-message-processor.js +587 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/server-admin.js +712 -0
- package/lib/server-auth.js +737 -0
- package/lib/server-dm.js +221 -0
- package/lib/server-mates.js +281 -0
- package/lib/server-palette.js +110 -0
- package/lib/server-settings.js +479 -0
- package/lib/server-skills.js +280 -0
- package/lib/server.js +246 -2755
- package/lib/sessions.js +11 -4
- 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
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* Edit modal: change cron/name/enabled for existing records.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { renderMarkdown } from './markdown.js';
|
|
9
8
|
import { iconHtml } from './icons.js';
|
|
10
9
|
import { showToast } from './utils.js';
|
|
10
|
+
import { initSchedulerConfig, setupCreateModal, openCreateModal, openCreateModalWithRecord, closeCreateModal, removePreview, getPreviewEl, showPreviewOnCell, showPreviewOnSlot, showPreviewForCreate, applyDraggedTask, parseCronSimple } from './scheduler-config.js';
|
|
11
|
+
import { initSchedulerHistory, renderHistory } from './scheduler-history.js';
|
|
12
|
+
export { handleLoopRegistryUpdated, handleLoopRegistryFiles, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled } from './scheduler-history.js';
|
|
11
13
|
|
|
12
14
|
var ctx = null;
|
|
13
15
|
var records = []; // all loop registry records
|
|
@@ -24,7 +26,6 @@ var showAllProjects = false; // toggle: show tasks from all projects (defa
|
|
|
24
26
|
var currentProjectSlug = null; // derived from basePath on init
|
|
25
27
|
var draggedTaskId = null; // drag-and-drop: task ID being dragged
|
|
26
28
|
var draggedTaskName = null; // drag-and-drop: task name being dragged
|
|
27
|
-
var previewEl = null; // temporary preview event element on calendar
|
|
28
29
|
var craftingTaskId = null; // task ID currently being crafted
|
|
29
30
|
var craftingSessionId = null; // session ID used for crafting
|
|
30
31
|
var logPreviousSessionId = null; // session to restore when leaving log mode
|
|
@@ -47,22 +48,8 @@ var inputOrigNextSibling = null; // anchor for restoring input-area position
|
|
|
47
48
|
|
|
48
49
|
// Edit state
|
|
49
50
|
|
|
50
|
-
// Create popover state
|
|
51
|
-
var createEditingRecId = null; // non-null when editing existing schedule
|
|
51
|
+
// Create popover state (most create* vars moved to scheduler-config.js)
|
|
52
52
|
var createPopover = null;
|
|
53
|
-
var createSelectedDate = null; // Date object for clicked calendar date
|
|
54
|
-
var createRecurrence = "none"; // current recurrence selection
|
|
55
|
-
var createCustomConfirmed = false; // whether custom repeat was confirmed via OK
|
|
56
|
-
var createInterval = "none"; // current interval selection: "none", "1", "5", "15", "30", "60", "custom"
|
|
57
|
-
var createIntervalCustom = null; // { value: N, unit: "minute"|"hour" } for custom interval
|
|
58
|
-
var createIntervalEnd = "allday"; // "allday" | "after" | "until"
|
|
59
|
-
var createIntervalEndAfter = 5; // occurrence count for "after" end type
|
|
60
|
-
var createIntervalEndTime = ""; // HH:MM string for "until" end type
|
|
61
|
-
var createColor = "#ffb86c"; // selected event color (default: accent)
|
|
62
|
-
var createEndType = "never"; // "never" | "until" | "after"
|
|
63
|
-
var createEndDate = null; // Date for "until" end type
|
|
64
|
-
var createEndCalMonth = null; // Date tracking displayed month in end calendar
|
|
65
|
-
var createEndAfter = 10; // occurrence count for "after" end type
|
|
66
53
|
var weekTzAbbr = ""; // cached timezone abbreviation for week view
|
|
67
54
|
var nowLineTimer = null; // interval timer for updating current-time indicator
|
|
68
55
|
|
|
@@ -96,9 +83,35 @@ export function initScheduler(_ctx) {
|
|
|
96
83
|
});
|
|
97
84
|
}
|
|
98
85
|
|
|
99
|
-
// Create modal
|
|
86
|
+
// Create modal (extracted to scheduler-config.js)
|
|
87
|
+
initSchedulerConfig({
|
|
88
|
+
ctx: ctx,
|
|
89
|
+
getRecords: function () { return records; },
|
|
90
|
+
getCreatePopover: function () { return createPopover; },
|
|
91
|
+
getContentCalEl: function () { return contentCalEl; },
|
|
92
|
+
getDragState: function () { return { draggedTaskId: draggedTaskId, draggedTaskName: draggedTaskName }; },
|
|
93
|
+
clearDragState: function () { draggedTaskId = null; draggedTaskName = null; },
|
|
94
|
+
send: send,
|
|
95
|
+
pad: pad,
|
|
96
|
+
esc: esc,
|
|
97
|
+
detectInterval: detectInterval,
|
|
98
|
+
});
|
|
100
99
|
setupCreateModal();
|
|
101
100
|
|
|
101
|
+
// History handlers (extracted to scheduler-history.js)
|
|
102
|
+
initSchedulerHistory({
|
|
103
|
+
isPanelOpen: function () { return panelOpen; },
|
|
104
|
+
getCurrentMode: function () { return currentMode; },
|
|
105
|
+
getSelectedTaskId: function () { return selectedTaskId; },
|
|
106
|
+
getContentDetailEl: function () { return contentDetailEl; },
|
|
107
|
+
setRecords: function (recs) { records = recs; },
|
|
108
|
+
renderSidebar: function () { renderSidebar(); },
|
|
109
|
+
render: function () { render(); },
|
|
110
|
+
renderDetail: function () { renderDetail(); },
|
|
111
|
+
send: send,
|
|
112
|
+
formatDateTime: formatDateTime,
|
|
113
|
+
});
|
|
114
|
+
|
|
102
115
|
// Close popover on outside click
|
|
103
116
|
document.addEventListener("click", function (e) {
|
|
104
117
|
if (popoverEl && !popoverEl.classList.contains("hidden") && !popoverEl.contains(e.target)) {
|
|
@@ -1402,21 +1415,6 @@ function attachEventClicks(container, selector) {
|
|
|
1402
1415
|
}
|
|
1403
1416
|
}
|
|
1404
1417
|
|
|
1405
|
-
function renderHistory(runs) {
|
|
1406
|
-
var el = document.getElementById("sched-history");
|
|
1407
|
-
if (!el || !runs || runs.length === 0) { if (el) el.innerHTML = '<div class="sched-history-empty">No runs yet</div>'; return; }
|
|
1408
|
-
var html = "";
|
|
1409
|
-
var sorted = runs.slice().reverse();
|
|
1410
|
-
for (var i = 0; i < sorted.length; i++) {
|
|
1411
|
-
var run = sorted[i];
|
|
1412
|
-
html += '<div class="sched-history-item"><span class="sched-history-dot ' + (run.result || "") + '"></span>';
|
|
1413
|
-
html += '<span class="sched-history-date">' + formatDateTime(new Date(run.startedAt)) + '</span>';
|
|
1414
|
-
html += '<span class="sched-history-result">' + (run.result || "?") + '</span>';
|
|
1415
|
-
html += '<span class="sched-history-iterations">' + (run.iterations || 0) + ' iter</span></div>';
|
|
1416
|
-
}
|
|
1417
|
-
el.innerHTML = html;
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
1418
|
// --- Public API ---
|
|
1421
1419
|
|
|
1422
1420
|
export function openSchedulerToTab(tab) {
|
|
@@ -1459,51 +1457,6 @@ export function exitCraftingMode(taskId) {
|
|
|
1459
1457
|
}
|
|
1460
1458
|
}
|
|
1461
1459
|
|
|
1462
|
-
// --- Message handlers ---
|
|
1463
|
-
|
|
1464
|
-
export function handleLoopRegistryUpdated(msg) {
|
|
1465
|
-
records = msg.records || [];
|
|
1466
|
-
if (panelOpen) {
|
|
1467
|
-
renderSidebar();
|
|
1468
|
-
if (currentMode === "calendar") render();
|
|
1469
|
-
else if (currentMode === "detail") renderDetail();
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
export function handleLoopRegistryFiles(msg) {
|
|
1474
|
-
if (!panelOpen || currentMode !== "detail") return;
|
|
1475
|
-
if (msg.id !== selectedTaskId) return;
|
|
1476
|
-
var bodyEl2 = document.getElementById("scheduler-detail-body");
|
|
1477
|
-
if (!bodyEl2) return;
|
|
1478
|
-
var activeTab = contentDetailEl ? contentDetailEl.querySelector(".scheduler-detail-tab.active") : null;
|
|
1479
|
-
var tab = activeTab ? activeTab.dataset.tab : "prompt";
|
|
1480
|
-
if (tab === "prompt") {
|
|
1481
|
-
bodyEl2.innerHTML = msg.prompt ? '<div class="md-content">' + renderMarkdown(msg.prompt) + '</div>' : '<div class="scheduler-empty">No PROMPT.md found</div>';
|
|
1482
|
-
} else if (tab === "judge") {
|
|
1483
|
-
bodyEl2.innerHTML = msg.judge ? '<div class="md-content">' + renderMarkdown(msg.judge) + '</div>' : '<div class="scheduler-empty">No JUDGE.md found</div>';
|
|
1484
|
-
}
|
|
1485
|
-
// Disable "Run now" if PROMPT.md or JUDGE.md is missing
|
|
1486
|
-
var runBtn = contentDetailEl ? contentDetailEl.querySelector('[data-action="run"]') : null;
|
|
1487
|
-
if (runBtn) {
|
|
1488
|
-
var filesReady = !!msg.prompt;
|
|
1489
|
-
runBtn.disabled = !filesReady;
|
|
1490
|
-
runBtn.title = filesReady ? "Run now" : "PROMPT.md is required to run";
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
export function handleScheduleRunStarted(msg) {
|
|
1495
|
-
if (panelOpen) render();
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
export function handleScheduleRunFinished(msg) {
|
|
1499
|
-
send({ type: "loop_registry_list" });
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
export function handleLoopScheduled(msg) {
|
|
1503
|
-
// A loop was just registered as scheduled (from approval bar)
|
|
1504
|
-
send({ type: "loop_registry_list" });
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1460
|
// Expose upcoming schedules (within given ms window) for countdown display
|
|
1508
1461
|
// Always filters to current project only (countdown is project-specific)
|
|
1509
1462
|
export function getUpcomingSchedules(windowMs) {
|
|
@@ -1545,7 +1498,7 @@ function attachCellClicks(container) {
|
|
|
1545
1498
|
e.preventDefault();
|
|
1546
1499
|
e.dataTransfer.dropEffect = "copy";
|
|
1547
1500
|
cell.classList.add("drag-over");
|
|
1548
|
-
if (!
|
|
1501
|
+
if (!getPreviewEl() || getPreviewEl().parentNode !== cell) {
|
|
1549
1502
|
showPreviewOnCell(cell);
|
|
1550
1503
|
}
|
|
1551
1504
|
});
|
|
@@ -1595,7 +1548,7 @@ function attachWeekSlotClicks(container) {
|
|
|
1595
1548
|
e.preventDefault();
|
|
1596
1549
|
e.dataTransfer.dropEffect = "copy";
|
|
1597
1550
|
slot.classList.add("drag-over");
|
|
1598
|
-
if (!
|
|
1551
|
+
if (!getPreviewEl() || !slot.closest(".scheduler-week-day-col").contains(getPreviewEl())) {
|
|
1599
1552
|
showPreviewOnSlot(slot);
|
|
1600
1553
|
}
|
|
1601
1554
|
});
|
|
@@ -1624,1480 +1577,6 @@ function attachWeekSlotClicks(container) {
|
|
|
1624
1577
|
}
|
|
1625
1578
|
}
|
|
1626
1579
|
|
|
1627
|
-
// --- Create Popover (inline, Akiflow-style) ---
|
|
1628
|
-
|
|
1629
|
-
function setupCreateModal() {
|
|
1630
|
-
if (!createPopover) return;
|
|
1631
|
-
|
|
1632
|
-
// Close
|
|
1633
|
-
document.getElementById("sched-create-cancel").addEventListener("click", function () { closeCreateModal(); });
|
|
1634
|
-
|
|
1635
|
-
// Color picker
|
|
1636
|
-
var colorBtn = document.getElementById("sched-create-color-btn");
|
|
1637
|
-
var colorPalette = document.getElementById("sched-create-color-palette");
|
|
1638
|
-
if (colorBtn && colorPalette) {
|
|
1639
|
-
colorBtn.addEventListener("click", function (e) {
|
|
1640
|
-
e.stopPropagation();
|
|
1641
|
-
colorPalette.classList.toggle("hidden");
|
|
1642
|
-
});
|
|
1643
|
-
var swatches = colorPalette.querySelectorAll(".sched-color-swatch");
|
|
1644
|
-
for (var i = 0; i < swatches.length; i++) {
|
|
1645
|
-
swatches[i].addEventListener("click", function (e) {
|
|
1646
|
-
e.stopPropagation();
|
|
1647
|
-
var c = this.dataset.color;
|
|
1648
|
-
createColor = c;
|
|
1649
|
-
var dot = document.getElementById("sched-create-color-dot");
|
|
1650
|
-
if (dot) dot.style.background = c;
|
|
1651
|
-
// update active state
|
|
1652
|
-
var all = colorPalette.querySelectorAll(".sched-color-swatch");
|
|
1653
|
-
for (var j = 0; j < all.length; j++) {
|
|
1654
|
-
all[j].classList.toggle("active", all[j].dataset.color === c);
|
|
1655
|
-
}
|
|
1656
|
-
colorPalette.classList.add("hidden");
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
// Date picker change → sync createSelectedDate and recurrence labels
|
|
1662
|
-
var datePickerEl = document.getElementById("sched-create-date-picker");
|
|
1663
|
-
if (datePickerEl) {
|
|
1664
|
-
datePickerEl.addEventListener("change", function () {
|
|
1665
|
-
var parts = this.value.split("-");
|
|
1666
|
-
if (parts.length === 3) {
|
|
1667
|
-
createSelectedDate = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
|
|
1668
|
-
document.getElementById("sched-create-date").value = this.value;
|
|
1669
|
-
updateRecurrenceLabels(createSelectedDate);
|
|
1670
|
-
enforceMinTime();
|
|
1671
|
-
}
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
// Time input change — enforce min time when today is selected
|
|
1676
|
-
var timeInputEl = document.getElementById("sched-create-time");
|
|
1677
|
-
if (timeInputEl) {
|
|
1678
|
-
timeInputEl.addEventListener("blur", function () {
|
|
1679
|
-
enforceMinTime();
|
|
1680
|
-
});
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
// Task dropdown
|
|
1684
|
-
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
1685
|
-
var taskList = document.getElementById("sched-create-task-list");
|
|
1686
|
-
if (taskBtn && taskList) {
|
|
1687
|
-
taskBtn.addEventListener("click", function (e) {
|
|
1688
|
-
e.stopPropagation();
|
|
1689
|
-
taskList.classList.toggle("hidden");
|
|
1690
|
-
});
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
// Close task dropdown on outside click
|
|
1694
|
-
document.addEventListener("click", function (e) {
|
|
1695
|
-
var tl = document.getElementById("sched-create-task-list");
|
|
1696
|
-
if (tl && !tl.classList.contains("hidden")) {
|
|
1697
|
-
if (!tl.contains(e.target) && !e.target.closest("#sched-create-task-btn")) {
|
|
1698
|
-
tl.classList.add("hidden");
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
});
|
|
1702
|
-
|
|
1703
|
-
// Recurrence button → toggle dropdown
|
|
1704
|
-
document.getElementById("sched-create-recurrence-btn").addEventListener("click", function (e) {
|
|
1705
|
-
e.stopPropagation();
|
|
1706
|
-
var dd = document.getElementById("sched-create-recurrence-dropdown");
|
|
1707
|
-
var btn = document.getElementById("sched-create-recurrence-btn");
|
|
1708
|
-
// Close interval dropdown if open
|
|
1709
|
-
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
1710
|
-
if (dd) {
|
|
1711
|
-
var wasHidden = dd.classList.contains("hidden");
|
|
1712
|
-
dd.classList.toggle("hidden");
|
|
1713
|
-
if (wasHidden && btn) {
|
|
1714
|
-
var bRect = btn.getBoundingClientRect();
|
|
1715
|
-
var ddW = 280;
|
|
1716
|
-
var ddLeft = bRect.left;
|
|
1717
|
-
if (ddLeft + ddW > window.innerWidth - 10) ddLeft = window.innerWidth - ddW - 10;
|
|
1718
|
-
if (ddLeft < 10) ddLeft = 10;
|
|
1719
|
-
dd.style.left = ddLeft + "px";
|
|
1720
|
-
dd.style.top = (bRect.bottom + 4) + "px";
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
});
|
|
1724
|
-
|
|
1725
|
-
// --- Interval button + dropdown ---
|
|
1726
|
-
var intervalSnapshot = null;
|
|
1727
|
-
document.getElementById("sched-create-interval-btn").addEventListener("click", function (e) {
|
|
1728
|
-
e.stopPropagation();
|
|
1729
|
-
var dd = document.getElementById("sched-create-interval-dropdown");
|
|
1730
|
-
var btn = document.getElementById("sched-create-interval-btn");
|
|
1731
|
-
// Close recurrence dropdown if open
|
|
1732
|
-
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
1733
|
-
if (dd) {
|
|
1734
|
-
var wasHidden = dd.classList.contains("hidden");
|
|
1735
|
-
dd.classList.toggle("hidden");
|
|
1736
|
-
if (wasHidden && btn) {
|
|
1737
|
-
// Save snapshot for cancel
|
|
1738
|
-
intervalSnapshot = {
|
|
1739
|
-
interval: createInterval,
|
|
1740
|
-
custom: createIntervalCustom ? { value: createIntervalCustom.value, unit: createIntervalCustom.unit } : null,
|
|
1741
|
-
end: createIntervalEnd,
|
|
1742
|
-
endAfter: createIntervalEndAfter,
|
|
1743
|
-
endTime: createIntervalEndTime
|
|
1744
|
-
};
|
|
1745
|
-
var bRect = btn.getBoundingClientRect();
|
|
1746
|
-
var ddW = 220;
|
|
1747
|
-
var ddLeft = bRect.left;
|
|
1748
|
-
if (ddLeft + ddW > window.innerWidth - 10) ddLeft = window.innerWidth - ddW - 10;
|
|
1749
|
-
if (ddLeft < 10) ddLeft = 10;
|
|
1750
|
-
dd.style.left = ddLeft + "px";
|
|
1751
|
-
dd.style.top = (bRect.bottom + 4) + "px";
|
|
1752
|
-
// Auto-apply custom interval when opening
|
|
1753
|
-
if (createInterval === "none") {
|
|
1754
|
-
applyInlineInterval();
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
});
|
|
1759
|
-
|
|
1760
|
-
// Interval Cancel button - revert to snapshot
|
|
1761
|
-
document.getElementById("sched-interval-cancel").addEventListener("click", function (e) {
|
|
1762
|
-
e.stopPropagation();
|
|
1763
|
-
if (intervalSnapshot) {
|
|
1764
|
-
createInterval = intervalSnapshot.interval;
|
|
1765
|
-
createIntervalCustom = intervalSnapshot.custom;
|
|
1766
|
-
createIntervalEnd = intervalSnapshot.end;
|
|
1767
|
-
createIntervalEndAfter = intervalSnapshot.endAfter;
|
|
1768
|
-
createIntervalEndTime = intervalSnapshot.endTime;
|
|
1769
|
-
updateIntervalBtn();
|
|
1770
|
-
}
|
|
1771
|
-
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
1772
|
-
});
|
|
1773
|
-
|
|
1774
|
-
// Interval OK button - accept and close
|
|
1775
|
-
document.getElementById("sched-interval-ok").addEventListener("click", function (e) {
|
|
1776
|
-
e.stopPropagation();
|
|
1777
|
-
applyInlineInterval();
|
|
1778
|
-
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
1779
|
-
});
|
|
1780
|
-
|
|
1781
|
-
// Interval custom input
|
|
1782
|
-
var intCustomValue = document.getElementById("sched-interval-custom-value");
|
|
1783
|
-
var intUnitSegs = document.querySelectorAll(".sched-interval-seg");
|
|
1784
|
-
function getIntervalUnit() {
|
|
1785
|
-
for (var s = 0; s < intUnitSegs.length; s++) {
|
|
1786
|
-
if (intUnitSegs[s].classList.contains("active")) return intUnitSegs[s].dataset.unit;
|
|
1787
|
-
}
|
|
1788
|
-
return "minute";
|
|
1789
|
-
}
|
|
1790
|
-
function applyInlineInterval() {
|
|
1791
|
-
var v = parseInt(intCustomValue.value, 10) || 1;
|
|
1792
|
-
var u = getIntervalUnit();
|
|
1793
|
-
createInterval = "custom";
|
|
1794
|
-
createIntervalCustom = { value: v, unit: u };
|
|
1795
|
-
updateIntervalBtn();
|
|
1796
|
-
}
|
|
1797
|
-
intCustomValue.addEventListener("change", applyInlineInterval);
|
|
1798
|
-
for (var si = 0; si < intUnitSegs.length; si++) {
|
|
1799
|
-
(function (seg) {
|
|
1800
|
-
seg.addEventListener("click", function (e) {
|
|
1801
|
-
e.stopPropagation();
|
|
1802
|
-
for (var s = 0; s < intUnitSegs.length; s++) {
|
|
1803
|
-
intUnitSegs[s].classList.toggle("active", intUnitSegs[s] === seg);
|
|
1804
|
-
}
|
|
1805
|
-
applyInlineInterval();
|
|
1806
|
-
});
|
|
1807
|
-
})(intUnitSegs[si]);
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
// Interval end condition options
|
|
1811
|
-
var iendOpts = document.querySelectorAll(".sched-interval-end-opt");
|
|
1812
|
-
for (var ie = 0; ie < iendOpts.length; ie++) {
|
|
1813
|
-
(function (opt) {
|
|
1814
|
-
opt.addEventListener("click", function (e) {
|
|
1815
|
-
e.stopPropagation();
|
|
1816
|
-
var val = opt.dataset.iend;
|
|
1817
|
-
for (var j = 0; j < iendOpts.length; j++) {
|
|
1818
|
-
iendOpts[j].classList.toggle("active", iendOpts[j] === opt);
|
|
1819
|
-
}
|
|
1820
|
-
createIntervalEnd = val;
|
|
1821
|
-
var afterRow = document.getElementById("sched-interval-end-after-row");
|
|
1822
|
-
var untilRow = document.getElementById("sched-interval-end-until-row");
|
|
1823
|
-
if (afterRow) afterRow.classList.toggle("hidden", val !== "after");
|
|
1824
|
-
if (untilRow) untilRow.classList.toggle("hidden", val !== "until");
|
|
1825
|
-
});
|
|
1826
|
-
})(iendOpts[ie]);
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
var iendAfterInput = document.getElementById("sched-interval-end-after");
|
|
1830
|
-
if (iendAfterInput) {
|
|
1831
|
-
iendAfterInput.addEventListener("change", function () {
|
|
1832
|
-
createIntervalEndAfter = parseInt(this.value, 10) || 5;
|
|
1833
|
-
if (createIntervalEndAfter < 1) createIntervalEndAfter = 1;
|
|
1834
|
-
});
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
var iendTimeInput = document.getElementById("sched-interval-end-time");
|
|
1838
|
-
if (iendTimeInput) {
|
|
1839
|
-
iendTimeInput.addEventListener("change", function () {
|
|
1840
|
-
createIntervalEndTime = this.value;
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
// Custom repeat: cancel
|
|
1845
|
-
document.getElementById("sched-custom-cancel").addEventListener("click", function (e) {
|
|
1846
|
-
e.stopPropagation();
|
|
1847
|
-
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
1848
|
-
});
|
|
1849
|
-
|
|
1850
|
-
// Custom repeat: unit change
|
|
1851
|
-
document.getElementById("sched-custom-unit").addEventListener("change", function () {
|
|
1852
|
-
var dowSection = document.getElementById("sched-custom-dow-section");
|
|
1853
|
-
if (dowSection) dowSection.style.display = this.value === "week" ? "" : "none";
|
|
1854
|
-
});
|
|
1855
|
-
|
|
1856
|
-
// Custom repeat: DOW toggle
|
|
1857
|
-
var customDowBtns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn");
|
|
1858
|
-
for (var i = 0; i < customDowBtns.length; i++) {
|
|
1859
|
-
(function (btn) {
|
|
1860
|
-
btn.addEventListener("click", function (e) { e.stopPropagation(); btn.classList.toggle("active"); });
|
|
1861
|
-
})(customDowBtns[i]);
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
// Custom repeat: End type JS dropdown
|
|
1865
|
-
var endBtn = document.getElementById("sched-custom-end-btn");
|
|
1866
|
-
var endList = document.getElementById("sched-custom-end-list");
|
|
1867
|
-
|
|
1868
|
-
endBtn.addEventListener("click", function (e) {
|
|
1869
|
-
e.stopPropagation();
|
|
1870
|
-
if (endList.classList.contains("hidden")) {
|
|
1871
|
-
var r = endBtn.getBoundingClientRect();
|
|
1872
|
-
endList.style.left = r.left + "px";
|
|
1873
|
-
endList.style.top = (r.bottom + 4) + "px";
|
|
1874
|
-
// If it would overflow bottom, show above
|
|
1875
|
-
endList.classList.remove("hidden");
|
|
1876
|
-
var lr = endList.getBoundingClientRect();
|
|
1877
|
-
if (lr.bottom > window.innerHeight - 8) {
|
|
1878
|
-
endList.style.top = (r.top - lr.height - 4) + "px";
|
|
1879
|
-
}
|
|
1880
|
-
} else {
|
|
1881
|
-
endList.classList.add("hidden");
|
|
1882
|
-
}
|
|
1883
|
-
});
|
|
1884
|
-
|
|
1885
|
-
var endItems = endList.querySelectorAll(".sched-custom-end-item");
|
|
1886
|
-
for (var ei = 0; ei < endItems.length; ei++) {
|
|
1887
|
-
(function (item) {
|
|
1888
|
-
item.addEventListener("click", function (e) {
|
|
1889
|
-
e.stopPropagation();
|
|
1890
|
-
var val = item.dataset.value;
|
|
1891
|
-
createEndType = val;
|
|
1892
|
-
document.getElementById("sched-custom-end").value = val;
|
|
1893
|
-
document.getElementById("sched-custom-end-label").textContent = item.textContent;
|
|
1894
|
-
|
|
1895
|
-
// Update active state
|
|
1896
|
-
for (var j = 0; j < endItems.length; j++) {
|
|
1897
|
-
endItems[j].classList.toggle("active", endItems[j] === item);
|
|
1898
|
-
}
|
|
1899
|
-
endList.classList.add("hidden");
|
|
1900
|
-
|
|
1901
|
-
// Toggle conditional inputs
|
|
1902
|
-
var dateBtn2 = document.getElementById("sched-custom-end-date-btn");
|
|
1903
|
-
var afterWrap = document.getElementById("sched-custom-end-after-wrap");
|
|
1904
|
-
var calPanel = document.getElementById("sched-custom-end-calendar");
|
|
1905
|
-
|
|
1906
|
-
dateBtn2.classList.add("hidden");
|
|
1907
|
-
afterWrap.classList.add("hidden");
|
|
1908
|
-
calPanel.classList.add("hidden");
|
|
1909
|
-
|
|
1910
|
-
if (val === "until") {
|
|
1911
|
-
dateBtn2.classList.remove("hidden");
|
|
1912
|
-
if (!createEndDate) {
|
|
1913
|
-
createEndDate = new Date(createSelectedDate || new Date());
|
|
1914
|
-
createEndDate.setMonth(createEndDate.getMonth() + 1);
|
|
1915
|
-
}
|
|
1916
|
-
updateEndDateLabel();
|
|
1917
|
-
} else if (val === "after") {
|
|
1918
|
-
afterWrap.classList.remove("hidden");
|
|
1919
|
-
document.getElementById("sched-custom-end-after").value = createEndAfter;
|
|
1920
|
-
}
|
|
1921
|
-
});
|
|
1922
|
-
})(endItems[ei]);
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
// Close end dropdown on outside click
|
|
1926
|
-
document.addEventListener("click", function (e) {
|
|
1927
|
-
if (endList && !endList.classList.contains("hidden")) {
|
|
1928
|
-
if (!endList.contains(e.target) && !endBtn.contains(e.target)) {
|
|
1929
|
-
endList.classList.add("hidden");
|
|
1930
|
-
}
|
|
1931
|
-
}
|
|
1932
|
-
});
|
|
1933
|
-
|
|
1934
|
-
// Custom repeat: End date button → toggle inline calendar
|
|
1935
|
-
document.getElementById("sched-custom-end-date-btn").addEventListener("click", function (e) {
|
|
1936
|
-
e.stopPropagation();
|
|
1937
|
-
var calPanel = document.getElementById("sched-custom-end-calendar");
|
|
1938
|
-
if (calPanel.classList.contains("hidden")) {
|
|
1939
|
-
createEndCalMonth = new Date(createEndDate.getFullYear(), createEndDate.getMonth(), 1);
|
|
1940
|
-
renderEndCalendar();
|
|
1941
|
-
calPanel.classList.remove("hidden");
|
|
1942
|
-
try { lucide.createIcons({ node: calPanel }); } catch (ex) {}
|
|
1943
|
-
} else {
|
|
1944
|
-
calPanel.classList.add("hidden");
|
|
1945
|
-
}
|
|
1946
|
-
});
|
|
1947
|
-
|
|
1948
|
-
// Custom repeat: End calendar prev/next
|
|
1949
|
-
document.getElementById("sched-cal-prev").addEventListener("click", function (e) {
|
|
1950
|
-
e.stopPropagation();
|
|
1951
|
-
createEndCalMonth.setMonth(createEndCalMonth.getMonth() - 1);
|
|
1952
|
-
renderEndCalendar();
|
|
1953
|
-
});
|
|
1954
|
-
document.getElementById("sched-cal-next").addEventListener("click", function (e) {
|
|
1955
|
-
e.stopPropagation();
|
|
1956
|
-
createEndCalMonth.setMonth(createEndCalMonth.getMonth() + 1);
|
|
1957
|
-
renderEndCalendar();
|
|
1958
|
-
});
|
|
1959
|
-
|
|
1960
|
-
// Custom repeat: After occurrences input
|
|
1961
|
-
document.getElementById("sched-custom-end-after").addEventListener("change", function () {
|
|
1962
|
-
createEndAfter = parseInt(this.value, 10) || 10;
|
|
1963
|
-
if (createEndAfter < 1) { createEndAfter = 1; this.value = 1; }
|
|
1964
|
-
});
|
|
1965
|
-
|
|
1966
|
-
// Custom repeat: OK
|
|
1967
|
-
document.getElementById("sched-custom-ok").addEventListener("click", function (e) {
|
|
1968
|
-
e.stopPropagation();
|
|
1969
|
-
createRecurrence = "custom";
|
|
1970
|
-
createCustomConfirmed = true;
|
|
1971
|
-
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
1972
|
-
updateRecurrenceBtn();
|
|
1973
|
-
});
|
|
1974
|
-
|
|
1975
|
-
// Run mode toggle (single vs multi-round)
|
|
1976
|
-
var runModeContainer = createPopover.querySelector(".sched-create-run-mode");
|
|
1977
|
-
if (runModeContainer) {
|
|
1978
|
-
runModeContainer.addEventListener("click", function (e) {
|
|
1979
|
-
var btn = e.target.closest(".sched-run-mode-btn");
|
|
1980
|
-
if (!btn) return;
|
|
1981
|
-
var mode = btn.dataset.mode;
|
|
1982
|
-
var btns = runModeContainer.querySelectorAll(".sched-run-mode-btn");
|
|
1983
|
-
for (var i = 0; i < btns.length; i++) btns[i].classList.toggle("active", btns[i] === btn);
|
|
1984
|
-
var iterGroup = document.getElementById("sched-create-iter-group");
|
|
1985
|
-
if (iterGroup) iterGroup.classList.toggle("hidden", mode !== "multi");
|
|
1986
|
-
});
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
// Submit
|
|
1990
|
-
document.getElementById("sched-create-submit").addEventListener("click", function () { submitCreateSchedule(); });
|
|
1991
|
-
|
|
1992
|
-
// Delete button → close popover, then open dialog
|
|
1993
|
-
var deleteBtn = document.getElementById("sched-create-delete");
|
|
1994
|
-
var deleteDialog = document.getElementById("sched-delete-dialog");
|
|
1995
|
-
if (deleteBtn) {
|
|
1996
|
-
deleteBtn.addEventListener("click", function (e) {
|
|
1997
|
-
e.stopPropagation();
|
|
1998
|
-
if (!createEditingRecId) return;
|
|
1999
|
-
var rec = null;
|
|
2000
|
-
for (var j = 0; j < records.length; j++) {
|
|
2001
|
-
if (records[j].id === createEditingRecId) { rec = records[j]; break; }
|
|
2002
|
-
}
|
|
2003
|
-
if (!rec) return;
|
|
2004
|
-
// Save context before closing popover
|
|
2005
|
-
var deleteRecId = createEditingRecId;
|
|
2006
|
-
var deleteDate = createSelectedDate ? new Date(createSelectedDate) : null;
|
|
2007
|
-
closeCreateModal();
|
|
2008
|
-
openDeleteDialog(deleteRecId, deleteDate, !rec.cron);
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
// Delete dialog option handlers
|
|
2013
|
-
if (deleteDialog) {
|
|
2014
|
-
var deleteOptions = deleteDialog.querySelectorAll(".sched-delete-option");
|
|
2015
|
-
for (var i = 0; i < deleteOptions.length; i++) {
|
|
2016
|
-
(function (opt) {
|
|
2017
|
-
opt.addEventListener("click", function (e) {
|
|
2018
|
-
e.stopPropagation();
|
|
2019
|
-
var action = opt.dataset.delete;
|
|
2020
|
-
if (action === "cancel") {
|
|
2021
|
-
closeDeleteDialog();
|
|
2022
|
-
return;
|
|
2023
|
-
}
|
|
2024
|
-
var recId = deleteDialog.dataset.recId;
|
|
2025
|
-
var dateStr = deleteDialog.dataset.eventDate;
|
|
2026
|
-
if (!recId) return;
|
|
2027
|
-
if (action === "this") {
|
|
2028
|
-
if (dateStr) {
|
|
2029
|
-
var dp = dateStr.split("-");
|
|
2030
|
-
var next = new Date(parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10));
|
|
2031
|
-
next.setDate(next.getDate() + 1);
|
|
2032
|
-
var newDate = next.getFullYear() + "-" + pad(next.getMonth() + 1) + "-" + pad(next.getDate());
|
|
2033
|
-
send({ type: "loop_registry_update", id: recId, data: { date: newDate } });
|
|
2034
|
-
}
|
|
2035
|
-
} else if (action === "following") {
|
|
2036
|
-
if (dateStr) {
|
|
2037
|
-
var dp2 = dateStr.split("-");
|
|
2038
|
-
var prev = new Date(parseInt(dp2[0], 10), parseInt(dp2[1], 10) - 1, parseInt(dp2[2], 10));
|
|
2039
|
-
prev.setDate(prev.getDate() - 1);
|
|
2040
|
-
var endDate = prev.getFullYear() + "-" + pad(prev.getMonth() + 1) + "-" + pad(prev.getDate());
|
|
2041
|
-
send({ type: "loop_registry_update", id: recId, data: { recurrenceEnd: { type: "until", date: endDate } } });
|
|
2042
|
-
}
|
|
2043
|
-
} else if (action === "all") {
|
|
2044
|
-
send({ type: "loop_registry_remove", id: recId });
|
|
2045
|
-
}
|
|
2046
|
-
closeDeleteDialog();
|
|
2047
|
-
});
|
|
2048
|
-
})(deleteOptions[i]);
|
|
2049
|
-
}
|
|
2050
|
-
// Close on backdrop click
|
|
2051
|
-
deleteDialog.addEventListener("click", function (e) {
|
|
2052
|
-
if (e.target === deleteDialog) closeDeleteDialog();
|
|
2053
|
-
});
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
// Close color palette on any click outside it
|
|
2057
|
-
document.addEventListener("click", function (e) {
|
|
2058
|
-
var pal = document.getElementById("sched-create-color-palette");
|
|
2059
|
-
if (pal && !pal.classList.contains("hidden")) {
|
|
2060
|
-
if (!pal.contains(e.target) && !e.target.closest("#sched-create-color-btn")) {
|
|
2061
|
-
pal.classList.add("hidden");
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
});
|
|
2065
|
-
|
|
2066
|
-
// Close popover on outside click
|
|
2067
|
-
document.addEventListener("click", function (e) {
|
|
2068
|
-
if (!createPopover || createPopover.classList.contains("hidden")) return;
|
|
2069
|
-
if (createPopover.contains(e.target)) return;
|
|
2070
|
-
// Also ignore clicks on calendar cells (they open the popover)
|
|
2071
|
-
if (e.target.closest(".scheduler-cell") || e.target.closest(".scheduler-week-slot")) return;
|
|
2072
|
-
closeCreateModal();
|
|
2073
|
-
});
|
|
2074
|
-
|
|
2075
|
-
// Escape key
|
|
2076
|
-
document.addEventListener("keydown", function (e) {
|
|
2077
|
-
if (e.key === "Escape" && createPopover && !createPopover.classList.contains("hidden")) {
|
|
2078
|
-
// Close recurrence dropdown first if open
|
|
2079
|
-
var dd = document.getElementById("sched-create-recurrence-dropdown");
|
|
2080
|
-
if (dd && !dd.classList.contains("hidden")) {
|
|
2081
|
-
dd.classList.add("hidden");
|
|
2082
|
-
return;
|
|
2083
|
-
}
|
|
2084
|
-
closeCreateModal();
|
|
2085
|
-
}
|
|
2086
|
-
});
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
function updateRecurrenceBtn() {
|
|
2090
|
-
var btn = document.getElementById("sched-create-recurrence-btn");
|
|
2091
|
-
if (btn) {
|
|
2092
|
-
btn.classList.toggle("has-recurrence", createRecurrence !== "none");
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
/**
|
|
2097
|
-
* When today is selected, set min on the time input so past times appear disabled.
|
|
2098
|
-
* If the current time value is before now, bump it to the next quarter-hour.
|
|
2099
|
-
*/
|
|
2100
|
-
function enforceMinTime() {
|
|
2101
|
-
var timeInput = document.getElementById("sched-create-time");
|
|
2102
|
-
var datePicker = document.getElementById("sched-create-date-picker");
|
|
2103
|
-
if (!timeInput || !datePicker) return;
|
|
2104
|
-
|
|
2105
|
-
var now = new Date();
|
|
2106
|
-
var todayStr = now.getFullYear() + "-" + pad(now.getMonth() + 1) + "-" + pad(now.getDate());
|
|
2107
|
-
var isToday = datePicker.value === todayStr;
|
|
2108
|
-
|
|
2109
|
-
if (isToday) {
|
|
2110
|
-
// Round up to the next minute for min
|
|
2111
|
-
var minMinutes = now.getHours() * 60 + now.getMinutes() + 1;
|
|
2112
|
-
var minH = Math.floor(minMinutes / 60);
|
|
2113
|
-
var minM = minMinutes % 60;
|
|
2114
|
-
if (minH >= 24) { minH = 23; minM = 59; }
|
|
2115
|
-
var minVal = pad(minH) + ":" + pad(minM);
|
|
2116
|
-
timeInput.min = minVal;
|
|
2117
|
-
|
|
2118
|
-
// If current value is before min, bump it
|
|
2119
|
-
if (timeInput.value < minVal) {
|
|
2120
|
-
timeInput.value = minVal;
|
|
2121
|
-
}
|
|
2122
|
-
} else {
|
|
2123
|
-
timeInput.removeAttribute("min");
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
function updateIntervalBtn() {
|
|
2128
|
-
var btn = document.getElementById("sched-create-interval-btn");
|
|
2129
|
-
if (btn) {
|
|
2130
|
-
btn.classList.toggle("has-recurrence", createInterval !== "none");
|
|
2131
|
-
}
|
|
2132
|
-
// Show/hide interval end conditions section
|
|
2133
|
-
var endSection = document.getElementById("sched-interval-end-section");
|
|
2134
|
-
if (endSection) {
|
|
2135
|
-
endSection.classList.toggle("hidden", createInterval === "none");
|
|
2136
|
-
}
|
|
2137
|
-
// Always show time picker (start time is needed even with interval)
|
|
2138
|
-
// Update skip-if-running visibility
|
|
2139
|
-
updateRecurrenceBtn();
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
function removePreview() {
|
|
2143
|
-
if (previewEl && previewEl.parentNode) {
|
|
2144
|
-
previewEl.parentNode.removeChild(previewEl);
|
|
2145
|
-
}
|
|
2146
|
-
previewEl = null;
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
function showPreviewOnCell(cell) {
|
|
2150
|
-
removePreview();
|
|
2151
|
-
var label = draggedTaskName || "(No title)";
|
|
2152
|
-
var el = document.createElement("div");
|
|
2153
|
-
el.className = "scheduler-event preview";
|
|
2154
|
-
el.textContent = label;
|
|
2155
|
-
cell.appendChild(el);
|
|
2156
|
-
previewEl = el;
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
function showPreviewOnSlot(slot) {
|
|
2160
|
-
removePreview();
|
|
2161
|
-
var label = draggedTaskName || "(No title)";
|
|
2162
|
-
var hour = parseInt(slot.dataset.hour, 10);
|
|
2163
|
-
var quarter = parseInt(slot.dataset.quarter || "0", 10);
|
|
2164
|
-
var minute = quarter * 15;
|
|
2165
|
-
var timeStr = pad(hour) + ":" + pad(minute);
|
|
2166
|
-
var col = slot.closest(".scheduler-week-day-col");
|
|
2167
|
-
if (!col) return;
|
|
2168
|
-
var topPct = ((hour * 60 + minute) / 1440) * 100;
|
|
2169
|
-
var el = document.createElement("div");
|
|
2170
|
-
el.className = "scheduler-week-event preview";
|
|
2171
|
-
el.style.cssText = "top:" + topPct + "%;height:calc(160vh / 48)";
|
|
2172
|
-
el.textContent = timeStr + " " + label;
|
|
2173
|
-
col.appendChild(el);
|
|
2174
|
-
previewEl = el;
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
function showPreviewForCreate(anchorEl, label) {
|
|
2178
|
-
removePreview();
|
|
2179
|
-
if (!anchorEl) return;
|
|
2180
|
-
var text = label || "(No title)";
|
|
2181
|
-
if (anchorEl.classList.contains("scheduler-week-slot")) {
|
|
2182
|
-
var hour = parseInt(anchorEl.dataset.hour, 10);
|
|
2183
|
-
var quarter = parseInt(anchorEl.dataset.quarter || "0", 10);
|
|
2184
|
-
var minute = quarter * 15;
|
|
2185
|
-
var timeStr = pad(hour) + ":" + pad(minute);
|
|
2186
|
-
var col = anchorEl.closest(".scheduler-week-day-col");
|
|
2187
|
-
if (!col) return;
|
|
2188
|
-
var topPct = ((hour * 60 + minute) / 1440) * 100;
|
|
2189
|
-
var el = document.createElement("div");
|
|
2190
|
-
el.className = "scheduler-week-event preview";
|
|
2191
|
-
el.style.cssText = "top:" + topPct + "%;height:calc(160vh / 48)";
|
|
2192
|
-
el.textContent = timeStr + " " + text;
|
|
2193
|
-
col.appendChild(el);
|
|
2194
|
-
previewEl = el;
|
|
2195
|
-
} else if (anchorEl.classList.contains("scheduler-cell")) {
|
|
2196
|
-
var el = document.createElement("div");
|
|
2197
|
-
el.className = "scheduler-event preview";
|
|
2198
|
-
el.textContent = text;
|
|
2199
|
-
anchorEl.appendChild(el);
|
|
2200
|
-
previewEl = el;
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
function applyDraggedTask() {
|
|
2205
|
-
if (!draggedTaskId) return;
|
|
2206
|
-
var taskHidden = document.getElementById("sched-create-task");
|
|
2207
|
-
var taskLabel = document.getElementById("sched-create-task-label");
|
|
2208
|
-
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
2209
|
-
if (taskHidden) taskHidden.value = draggedTaskId;
|
|
2210
|
-
if (taskLabel) taskLabel.textContent = draggedTaskName || draggedTaskId;
|
|
2211
|
-
if (taskBtn) { taskBtn.classList.add("has-value"); taskBtn.classList.remove("invalid"); }
|
|
2212
|
-
// Mark the matching item as selected in the dropdown list
|
|
2213
|
-
var taskListEl = document.getElementById("sched-create-task-list");
|
|
2214
|
-
if (taskListEl) {
|
|
2215
|
-
var items = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
2216
|
-
for (var k = 0; k < items.length; k++) {
|
|
2217
|
-
items[k].classList.toggle("selected", items[k].dataset.taskId === draggedTaskId);
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
// Auto-generate title: "taskName - HH:MM"
|
|
2221
|
-
var titleInput = document.getElementById("sched-create-title");
|
|
2222
|
-
var timeInput = document.getElementById("sched-create-time");
|
|
2223
|
-
if (titleInput && (draggedTaskName || draggedTaskId)) {
|
|
2224
|
-
var name = draggedTaskName || draggedTaskId;
|
|
2225
|
-
var time = timeInput ? timeInput.value : "";
|
|
2226
|
-
titleInput.value = time ? name + " - " + time : name;
|
|
2227
|
-
}
|
|
2228
|
-
// Update preview text to match auto-title
|
|
2229
|
-
if (previewEl && titleInput) {
|
|
2230
|
-
var previewText = titleInput.value || "(No title)";
|
|
2231
|
-
if (previewEl.classList.contains("scheduler-week-event") && timeInput) {
|
|
2232
|
-
previewText = timeInput.value + " " + (titleInput.value || "(No title)");
|
|
2233
|
-
}
|
|
2234
|
-
previewEl.textContent = previewText;
|
|
2235
|
-
}
|
|
2236
|
-
draggedTaskId = null;
|
|
2237
|
-
draggedTaskName = null;
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
function openCreateModalWithRecord(rec, anchorEl) {
|
|
2241
|
-
// Parse date/time from record
|
|
2242
|
-
var date = null;
|
|
2243
|
-
var hour = null;
|
|
2244
|
-
if (rec.date) {
|
|
2245
|
-
var dp = rec.date.split("-");
|
|
2246
|
-
date = new Date(parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10));
|
|
2247
|
-
}
|
|
2248
|
-
if (rec.time) {
|
|
2249
|
-
var tp = rec.time.split(":");
|
|
2250
|
-
hour = parseInt(tp[0], 10) || 0;
|
|
2251
|
-
var mins = parseInt(tp[1], 10) || 0;
|
|
2252
|
-
if (date) { date.setHours(hour, mins, 0); }
|
|
2253
|
-
}
|
|
2254
|
-
// Mark as editing existing record
|
|
2255
|
-
createEditingRecId = rec.id;
|
|
2256
|
-
|
|
2257
|
-
// Open the create modal normally first
|
|
2258
|
-
openCreateModal(date || new Date(), hour, anchorEl);
|
|
2259
|
-
|
|
2260
|
-
// Show delete button
|
|
2261
|
-
var deleteBtn = document.getElementById("sched-create-delete");
|
|
2262
|
-
if (deleteBtn) deleteBtn.classList.remove("hidden");
|
|
2263
|
-
|
|
2264
|
-
// Now override with record values
|
|
2265
|
-
var titleInput = document.getElementById("sched-create-title");
|
|
2266
|
-
if (titleInput) titleInput.value = rec.name || "";
|
|
2267
|
-
|
|
2268
|
-
var descInput = document.getElementById("sched-create-desc");
|
|
2269
|
-
if (descInput) descInput.value = rec.description || "";
|
|
2270
|
-
|
|
2271
|
-
// Set color
|
|
2272
|
-
if (rec.color) {
|
|
2273
|
-
createColor = rec.color;
|
|
2274
|
-
var colorDot = document.getElementById("sched-create-color-dot");
|
|
2275
|
-
if (colorDot) colorDot.style.background = createColor;
|
|
2276
|
-
var swatches = createPopover.querySelectorAll(".sched-color-swatch");
|
|
2277
|
-
for (var si = 0; si < swatches.length; si++) {
|
|
2278
|
-
swatches[si].classList.toggle("active", swatches[si].dataset.color === createColor);
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
// Set skip-if-running
|
|
2283
|
-
var skipRunningEl = document.getElementById("sched-skip-running");
|
|
2284
|
-
if (skipRunningEl) skipRunningEl.checked = rec.skipIfRunning !== false;
|
|
2285
|
-
|
|
2286
|
-
// Set run mode and iterations
|
|
2287
|
-
var editRunMode = (rec.maxIterations && rec.maxIterations > 1) ? "multi" : "single";
|
|
2288
|
-
var editRunBtns = createPopover.querySelectorAll(".sched-run-mode-btn");
|
|
2289
|
-
for (var rb = 0; rb < editRunBtns.length; rb++) {
|
|
2290
|
-
editRunBtns[rb].classList.toggle("active", editRunBtns[rb].dataset.mode === editRunMode);
|
|
2291
|
-
}
|
|
2292
|
-
var editIterGroup = document.getElementById("sched-create-iter-group");
|
|
2293
|
-
if (editIterGroup) editIterGroup.classList.toggle("hidden", editRunMode !== "multi");
|
|
2294
|
-
if (rec.maxIterations && rec.maxIterations > 1) {
|
|
2295
|
-
var iterInput = document.getElementById("sched-create-iterations");
|
|
2296
|
-
if (iterInput) iterInput.value = rec.maxIterations;
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
// Set linked task
|
|
2300
|
-
if (rec.linkedTaskId) {
|
|
2301
|
-
var taskHidden = document.getElementById("sched-create-task");
|
|
2302
|
-
var taskLabel = document.getElementById("sched-create-task-label");
|
|
2303
|
-
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
2304
|
-
var taskListEl = document.getElementById("sched-create-task-list");
|
|
2305
|
-
if (taskHidden) taskHidden.value = rec.linkedTaskId;
|
|
2306
|
-
// Find the task name
|
|
2307
|
-
var taskName = rec.linkedTaskId;
|
|
2308
|
-
for (var j = 0; j < records.length; j++) {
|
|
2309
|
-
if (records[j].id === rec.linkedTaskId) { taskName = records[j].name || records[j].id; break; }
|
|
2310
|
-
}
|
|
2311
|
-
if (taskLabel) taskLabel.textContent = taskName;
|
|
2312
|
-
if (taskBtn) { taskBtn.classList.add("has-value"); taskBtn.classList.remove("invalid"); }
|
|
2313
|
-
if (taskListEl) {
|
|
2314
|
-
var items = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
2315
|
-
for (var k = 0; k < items.length; k++) {
|
|
2316
|
-
items[k].classList.toggle("selected", items[k].dataset.taskId === rec.linkedTaskId);
|
|
2317
|
-
}
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
|
-
// Restore interval from cron
|
|
2322
|
-
if (rec.cron) {
|
|
2323
|
-
var cronParts = rec.cron.trim().split(/\s+/);
|
|
2324
|
-
if (cronParts.length === 5) {
|
|
2325
|
-
var detectedMinInterval = null;
|
|
2326
|
-
var detectedHrInterval = null;
|
|
2327
|
-
// Detect minute-level interval: e.g. "0,5,10,... * * * *" or "*/5 * * * *"
|
|
2328
|
-
if (cronParts[1] === "*" && cronParts[2] === "*") {
|
|
2329
|
-
detectedMinInterval = detectInterval(cronParts[0], 60);
|
|
2330
|
-
}
|
|
2331
|
-
// Detect hour-level interval: e.g. "0 1,3,5,... * * *"
|
|
2332
|
-
if (!detectedMinInterval && cronParts[2] === "*") {
|
|
2333
|
-
detectedHrInterval = detectInterval(cronParts[1], 24);
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
if (detectedMinInterval) {
|
|
2337
|
-
createInterval = "custom";
|
|
2338
|
-
createIntervalCustom = { value: detectedMinInterval, unit: "minute" };
|
|
2339
|
-
var intValEl = document.getElementById("sched-interval-custom-value");
|
|
2340
|
-
if (intValEl) intValEl.value = detectedMinInterval;
|
|
2341
|
-
var intUnitBtns = document.querySelectorAll("#sched-interval-custom-unit .sched-interval-seg");
|
|
2342
|
-
for (var iu = 0; iu < intUnitBtns.length; iu++) {
|
|
2343
|
-
intUnitBtns[iu].classList.toggle("active", intUnitBtns[iu].dataset.unit === "minute");
|
|
2344
|
-
}
|
|
2345
|
-
updateIntervalBtn();
|
|
2346
|
-
} else if (detectedHrInterval) {
|
|
2347
|
-
createInterval = "custom";
|
|
2348
|
-
createIntervalCustom = { value: detectedHrInterval, unit: "hour" };
|
|
2349
|
-
var intValEl2 = document.getElementById("sched-interval-custom-value");
|
|
2350
|
-
if (intValEl2) intValEl2.value = detectedHrInterval;
|
|
2351
|
-
var intUnitBtns2 = document.querySelectorAll("#sched-interval-custom-unit .sched-interval-seg");
|
|
2352
|
-
for (var iu2 = 0; iu2 < intUnitBtns2.length; iu2++) {
|
|
2353
|
-
intUnitBtns2[iu2].classList.toggle("active", intUnitBtns2[iu2].dataset.unit === "hour");
|
|
2354
|
-
}
|
|
2355
|
-
updateIntervalBtn();
|
|
2356
|
-
}
|
|
2357
|
-
|
|
2358
|
-
// Restore recurrence from cron (if no interval detected, or combined with interval)
|
|
2359
|
-
if (!detectedMinInterval && !detectedHrInterval) {
|
|
2360
|
-
var cronDow = cronParts[4];
|
|
2361
|
-
var cronDom = cronParts[2];
|
|
2362
|
-
var cronMonth = cronParts[3];
|
|
2363
|
-
if (cronDow === "*" && cronDom === "*" && cronMonth === "*") {
|
|
2364
|
-
createRecurrence = "daily";
|
|
2365
|
-
} else if (cronDow === "1-5" && cronDom === "*") {
|
|
2366
|
-
createRecurrence = "weekdays";
|
|
2367
|
-
} else if (cronDom !== "*" && cronMonth !== "*") {
|
|
2368
|
-
createRecurrence = "yearly";
|
|
2369
|
-
} else if (cronDom !== "*" && cronDow === "*") {
|
|
2370
|
-
createRecurrence = "monthly";
|
|
2371
|
-
} else if (cronDow !== "*" && cronDom === "*") {
|
|
2372
|
-
// Check if it matches a single day (weekly)
|
|
2373
|
-
var dowVals = cronDow.split(",");
|
|
2374
|
-
if (dowVals.length === 1) {
|
|
2375
|
-
createRecurrence = "weekly";
|
|
2376
|
-
} else if (dowVals.length === 7) {
|
|
2377
|
-
createRecurrence = "daily";
|
|
2378
|
-
} else {
|
|
2379
|
-
createRecurrence = "custom";
|
|
2380
|
-
createCustomConfirmed = true;
|
|
2381
|
-
// Set custom panel values
|
|
2382
|
-
document.getElementById("sched-custom-interval").value = "1";
|
|
2383
|
-
document.getElementById("sched-custom-unit").value = "week";
|
|
2384
|
-
var customDowBtns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn");
|
|
2385
|
-
for (var cd = 0; cd < customDowBtns.length; cd++) {
|
|
2386
|
-
customDowBtns[cd].classList.toggle("active", dowVals.indexOf(customDowBtns[cd].dataset.dow) !== -1);
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
updateRecurrenceBtn();
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
// Restore interval end conditions
|
|
2396
|
-
if (rec.intervalEnd) {
|
|
2397
|
-
createIntervalEnd = rec.intervalEnd.type || "allday";
|
|
2398
|
-
var editIendOpts = document.querySelectorAll(".sched-interval-end-opt");
|
|
2399
|
-
for (var ei = 0; ei < editIendOpts.length; ei++) {
|
|
2400
|
-
editIendOpts[ei].classList.toggle("active", editIendOpts[ei].dataset.iend === createIntervalEnd);
|
|
2401
|
-
}
|
|
2402
|
-
var editAfterRow = document.getElementById("sched-interval-end-after-row");
|
|
2403
|
-
var editUntilRow = document.getElementById("sched-interval-end-until-row");
|
|
2404
|
-
if (createIntervalEnd === "after") {
|
|
2405
|
-
createIntervalEndAfter = rec.intervalEnd.count || 5;
|
|
2406
|
-
if (editAfterRow) editAfterRow.classList.remove("hidden");
|
|
2407
|
-
if (editUntilRow) editUntilRow.classList.add("hidden");
|
|
2408
|
-
var editAfterInput = document.getElementById("sched-interval-end-after");
|
|
2409
|
-
if (editAfterInput) editAfterInput.value = createIntervalEndAfter;
|
|
2410
|
-
} else if (createIntervalEnd === "until") {
|
|
2411
|
-
createIntervalEndTime = rec.intervalEnd.time || "18:00";
|
|
2412
|
-
if (editAfterRow) editAfterRow.classList.add("hidden");
|
|
2413
|
-
if (editUntilRow) editUntilRow.classList.remove("hidden");
|
|
2414
|
-
var editTimeInput = document.getElementById("sched-interval-end-time");
|
|
2415
|
-
if (editTimeInput) editTimeInput.value = createIntervalEndTime;
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
// Update preview to show record name
|
|
2420
|
-
if (previewEl) {
|
|
2421
|
-
var previewText = rec.name || "(No title)";
|
|
2422
|
-
if (previewEl.classList.contains("scheduler-week-event") && rec.time) {
|
|
2423
|
-
previewText = rec.time + " " + previewText;
|
|
2424
|
-
}
|
|
2425
|
-
previewEl.textContent = previewText;
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2429
|
-
function openCreateModal(date, hour, anchorEl) {
|
|
2430
|
-
if (!createPopover) return;
|
|
2431
|
-
// Reset editing state (openCreateModalWithRecord sets this before calling us)
|
|
2432
|
-
if (!createEditingRecId) {
|
|
2433
|
-
var deleteBtn = document.getElementById("sched-create-delete");
|
|
2434
|
-
if (deleteBtn) deleteBtn.classList.add("hidden");
|
|
2435
|
-
}
|
|
2436
|
-
createSelectedDate = date || new Date();
|
|
2437
|
-
createRecurrence = "none";
|
|
2438
|
-
createCustomConfirmed = false;
|
|
2439
|
-
createInterval = "none";
|
|
2440
|
-
createIntervalCustom = null;
|
|
2441
|
-
createIntervalEnd = "allday";
|
|
2442
|
-
createIntervalEndAfter = 5;
|
|
2443
|
-
createIntervalEndTime = "";
|
|
2444
|
-
createColor = "#ffb86c";
|
|
2445
|
-
|
|
2446
|
-
// Reset form
|
|
2447
|
-
document.getElementById("sched-create-title").value = "";
|
|
2448
|
-
document.getElementById("sched-create-desc").value = "";
|
|
2449
|
-
var iterReset = document.getElementById("sched-create-iterations");
|
|
2450
|
-
if (iterReset) iterReset.value = "3";
|
|
2451
|
-
// Reset run mode to single
|
|
2452
|
-
var runModeBtns = createPopover.querySelectorAll(".sched-run-mode-btn");
|
|
2453
|
-
for (var rm = 0; rm < runModeBtns.length; rm++) {
|
|
2454
|
-
runModeBtns[rm].classList.toggle("active", runModeBtns[rm].dataset.mode === "single");
|
|
2455
|
-
}
|
|
2456
|
-
var iterGroup = document.getElementById("sched-create-iter-group");
|
|
2457
|
-
if (iterGroup) iterGroup.classList.add("hidden");
|
|
2458
|
-
|
|
2459
|
-
// Reset color
|
|
2460
|
-
var colorDot = document.getElementById("sched-create-color-dot");
|
|
2461
|
-
if (colorDot) colorDot.style.background = createColor;
|
|
2462
|
-
var palette = document.getElementById("sched-create-color-palette");
|
|
2463
|
-
if (palette) palette.classList.add("hidden");
|
|
2464
|
-
var swatches = createPopover.querySelectorAll(".sched-color-swatch");
|
|
2465
|
-
for (var si = 0; si < swatches.length; si++) {
|
|
2466
|
-
swatches[si].classList.toggle("active", swatches[si].dataset.color === createColor);
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
// Populate task dropdown (only tasks — exclude ralph and schedule)
|
|
2470
|
-
var taskHidden = document.getElementById("sched-create-task");
|
|
2471
|
-
var taskLabel = document.getElementById("sched-create-task-label");
|
|
2472
|
-
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
2473
|
-
var taskListEl = document.getElementById("sched-create-task-list");
|
|
2474
|
-
if (taskHidden) taskHidden.value = "";
|
|
2475
|
-
if (taskLabel) taskLabel.textContent = "Select a task";
|
|
2476
|
-
if (taskBtn) { taskBtn.classList.remove("has-value"); taskBtn.classList.remove("invalid"); }
|
|
2477
|
-
if (taskListEl) {
|
|
2478
|
-
taskListEl.classList.add("hidden");
|
|
2479
|
-
var tasks = records.filter(function (r) { return r.source !== "ralph" && r.source !== "schedule"; });
|
|
2480
|
-
if (tasks.length === 0) {
|
|
2481
|
-
taskListEl.innerHTML = '<div class="sched-create-task-empty">No tasks available</div>';
|
|
2482
|
-
} else {
|
|
2483
|
-
var html = "";
|
|
2484
|
-
for (var i = 0; i < tasks.length; i++) {
|
|
2485
|
-
html += '<div class="sched-create-task-item" data-task-id="' + esc(tasks[i].id) + '">' + esc(tasks[i].name || tasks[i].id) + '</div>';
|
|
2486
|
-
}
|
|
2487
|
-
taskListEl.innerHTML = html;
|
|
2488
|
-
// Bind click handlers
|
|
2489
|
-
var items = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
2490
|
-
for (var j = 0; j < items.length; j++) {
|
|
2491
|
-
(function (item) {
|
|
2492
|
-
item.addEventListener("click", function (e) {
|
|
2493
|
-
e.stopPropagation();
|
|
2494
|
-
var id = item.dataset.taskId;
|
|
2495
|
-
var name = item.textContent;
|
|
2496
|
-
if (taskHidden) taskHidden.value = id;
|
|
2497
|
-
if (taskLabel) taskLabel.textContent = name;
|
|
2498
|
-
if (taskBtn) { taskBtn.classList.add("has-value"); taskBtn.classList.remove("invalid"); }
|
|
2499
|
-
// Update selected state
|
|
2500
|
-
var all = taskListEl.querySelectorAll(".sched-create-task-item");
|
|
2501
|
-
for (var k = 0; k < all.length; k++) {
|
|
2502
|
-
all[k].classList.toggle("selected", all[k] === item);
|
|
2503
|
-
}
|
|
2504
|
-
taskListEl.classList.add("hidden");
|
|
2505
|
-
});
|
|
2506
|
-
})(items[j]);
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
// Set date picker
|
|
2512
|
-
var dateStr = createSelectedDate.getFullYear() + "-" + pad(createSelectedDate.getMonth() + 1) + "-" + pad(createSelectedDate.getDate());
|
|
2513
|
-
document.getElementById("sched-create-date").value = dateStr;
|
|
2514
|
-
var datePicker = document.getElementById("sched-create-date-picker");
|
|
2515
|
-
if (datePicker) {
|
|
2516
|
-
datePicker.value = dateStr;
|
|
2517
|
-
var todayNow = new Date();
|
|
2518
|
-
var todayMin = todayNow.getFullYear() + "-" + pad(todayNow.getMonth() + 1) + "-" + pad(todayNow.getDate());
|
|
2519
|
-
datePicker.min = todayMin;
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
// Time (use minutes from createSelectedDate for 15-min snapping)
|
|
2523
|
-
if (hour !== null && hour !== undefined) {
|
|
2524
|
-
var mins = createSelectedDate.getMinutes ? createSelectedDate.getMinutes() : 0;
|
|
2525
|
-
document.getElementById("sched-create-time").value = pad(hour) + ":" + pad(mins);
|
|
2526
|
-
} else {
|
|
2527
|
-
// Default to current time (next quarter-hour)
|
|
2528
|
-
var nowT = new Date();
|
|
2529
|
-
var nowMins = nowT.getHours() * 60 + nowT.getMinutes();
|
|
2530
|
-
var nextQ = Math.ceil(nowMins / 15) * 15;
|
|
2531
|
-
var defH = Math.floor(nextQ / 60);
|
|
2532
|
-
var defM = nextQ % 60;
|
|
2533
|
-
if (defH >= 24) { defH = 23; defM = 45; }
|
|
2534
|
-
document.getElementById("sched-create-time").value = pad(defH) + ":" + pad(defM);
|
|
2535
|
-
}
|
|
2536
|
-
|
|
2537
|
-
// Update recurrence labels
|
|
2538
|
-
updateRecurrenceLabels(createSelectedDate);
|
|
2539
|
-
|
|
2540
|
-
// Enforce min time for today
|
|
2541
|
-
enforceMinTime();
|
|
2542
|
-
|
|
2543
|
-
// Reset recurrence
|
|
2544
|
-
createRecurrence = "none";
|
|
2545
|
-
createCustomConfirmed = false;
|
|
2546
|
-
updateRecurrenceBtn();
|
|
2547
|
-
|
|
2548
|
-
// Reset interval
|
|
2549
|
-
document.getElementById("sched-create-interval-dropdown").classList.add("hidden");
|
|
2550
|
-
var timeInput = document.getElementById("sched-create-time");
|
|
2551
|
-
if (timeInput) timeInput.style.display = "";
|
|
2552
|
-
|
|
2553
|
-
// Reset interval end conditions
|
|
2554
|
-
var iendOpts = document.querySelectorAll(".sched-interval-end-opt");
|
|
2555
|
-
for (var ie = 0; ie < iendOpts.length; ie++) {
|
|
2556
|
-
iendOpts[ie].classList.toggle("active", iendOpts[ie].dataset.iend === "allday");
|
|
2557
|
-
}
|
|
2558
|
-
var iendAfterRow = document.getElementById("sched-interval-end-after-row");
|
|
2559
|
-
if (iendAfterRow) iendAfterRow.classList.add("hidden");
|
|
2560
|
-
var iendUntilRow = document.getElementById("sched-interval-end-until-row");
|
|
2561
|
-
if (iendUntilRow) iendUntilRow.classList.add("hidden");
|
|
2562
|
-
var iendAfterInput = document.getElementById("sched-interval-end-after");
|
|
2563
|
-
if (iendAfterInput) iendAfterInput.value = "5";
|
|
2564
|
-
var iendTimeInput = document.getElementById("sched-interval-end-time");
|
|
2565
|
-
if (iendTimeInput) iendTimeInput.value = "18:00";
|
|
2566
|
-
|
|
2567
|
-
updateIntervalBtn();
|
|
2568
|
-
|
|
2569
|
-
// Reset custom panel
|
|
2570
|
-
document.getElementById("sched-create-recurrence-dropdown").classList.add("hidden");
|
|
2571
|
-
document.getElementById("sched-custom-interval").value = "1";
|
|
2572
|
-
document.getElementById("sched-custom-unit").value = "week";
|
|
2573
|
-
document.getElementById("sched-custom-dow-section").style.display = "";
|
|
2574
|
-
var customDowBtns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn");
|
|
2575
|
-
for (var i = 0; i < customDowBtns.length; i++) {
|
|
2576
|
-
customDowBtns[i].classList.toggle("active", parseInt(customDowBtns[i].dataset.dow) === createSelectedDate.getDay());
|
|
2577
|
-
}
|
|
2578
|
-
document.getElementById("sched-custom-end").value = "never";
|
|
2579
|
-
document.getElementById("sched-custom-end-label").textContent = "Never";
|
|
2580
|
-
var endItems = document.querySelectorAll(".sched-custom-end-item");
|
|
2581
|
-
for (var ei = 0; ei < endItems.length; ei++) {
|
|
2582
|
-
endItems[ei].classList.toggle("active", endItems[ei].dataset.value === "never");
|
|
2583
|
-
}
|
|
2584
|
-
document.getElementById("sched-custom-end-list").classList.add("hidden");
|
|
2585
|
-
createEndType = "never";
|
|
2586
|
-
createEndDate = null;
|
|
2587
|
-
createEndAfter = 10;
|
|
2588
|
-
document.getElementById("sched-custom-end-date-btn").classList.add("hidden");
|
|
2589
|
-
document.getElementById("sched-custom-end-after-wrap").classList.add("hidden");
|
|
2590
|
-
document.getElementById("sched-custom-end-calendar").classList.add("hidden");
|
|
2591
|
-
|
|
2592
|
-
// Show preview event on the calendar cell
|
|
2593
|
-
showPreviewForCreate(anchorEl, draggedTaskName || null);
|
|
2594
|
-
|
|
2595
|
-
// Position near anchor cell
|
|
2596
|
-
createPopover.classList.remove("hidden");
|
|
2597
|
-
positionCreatePopover(anchorEl);
|
|
2598
|
-
|
|
2599
|
-
try { lucide.createIcons({ node: createPopover }); } catch (e) {}
|
|
2600
|
-
setTimeout(function () { document.getElementById("sched-create-title").focus(); }, 50);
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
function positionCreatePopover(anchorEl) {
|
|
2604
|
-
if (!createPopover || !anchorEl) {
|
|
2605
|
-
// Fallback: center in scheduler content area
|
|
2606
|
-
if (createPopover && contentCalEl) {
|
|
2607
|
-
var cRect = contentCalEl.getBoundingClientRect();
|
|
2608
|
-
createPopover.style.left = (cRect.left + cRect.width / 2 - 180) + "px";
|
|
2609
|
-
createPopover.style.top = (cRect.top + 60) + "px";
|
|
2610
|
-
}
|
|
2611
|
-
return;
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
var rect = anchorEl.getBoundingClientRect();
|
|
2615
|
-
var popW = 360;
|
|
2616
|
-
var popH = createPopover.offsetHeight || 300;
|
|
2617
|
-
|
|
2618
|
-
// Try to place to the right of the cell
|
|
2619
|
-
var left = rect.right + 8;
|
|
2620
|
-
var top = rect.top;
|
|
2621
|
-
|
|
2622
|
-
// If it overflows right, place to the left
|
|
2623
|
-
if (left + popW > window.innerWidth - 10) {
|
|
2624
|
-
left = rect.left - popW - 8;
|
|
2625
|
-
}
|
|
2626
|
-
// If it still overflows left, center horizontally on the cell
|
|
2627
|
-
if (left < 10) {
|
|
2628
|
-
left = Math.max(10, rect.left + rect.width / 2 - popW / 2);
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
// Vertical: don't overflow bottom
|
|
2632
|
-
if (top + popH > window.innerHeight - 10) {
|
|
2633
|
-
top = window.innerHeight - popH - 10;
|
|
2634
|
-
}
|
|
2635
|
-
if (top < 10) top = 10;
|
|
2636
|
-
|
|
2637
|
-
createPopover.style.left = left + "px";
|
|
2638
|
-
createPopover.style.top = top + "px";
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
function updateRecurrenceLabels(date) {
|
|
2642
|
-
var dow = date.getDay();
|
|
2643
|
-
var dayName = DAY_NAMES[dow];
|
|
2644
|
-
var dom = date.getDate();
|
|
2645
|
-
var monthName = MONTH_NAMES[date.getMonth()];
|
|
2646
|
-
|
|
2647
|
-
// Weekly on {day}
|
|
2648
|
-
var weeklyEl = document.getElementById("sched-recurrence-weekly");
|
|
2649
|
-
if (weeklyEl) weeklyEl.textContent = "Weekly on " + dayName;
|
|
2650
|
-
|
|
2651
|
-
// Every second {day} of the month
|
|
2652
|
-
var weekOfMonth = Math.ceil(dom / 7);
|
|
2653
|
-
var ordinals = ["", "first", "second", "third", "fourth", "fifth"];
|
|
2654
|
-
var biweeklyEl = document.getElementById("sched-recurrence-biweekly");
|
|
2655
|
-
if (biweeklyEl) {
|
|
2656
|
-
var ordStr = ordinals[weekOfMonth] || weekOfMonth + "th";
|
|
2657
|
-
biweeklyEl.textContent = "Every " + ordStr + " " + dayName + " of the mo...";
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
// Every year on {month} {date}
|
|
2661
|
-
var yearlyEl = document.getElementById("sched-recurrence-yearly");
|
|
2662
|
-
if (yearlyEl) yearlyEl.textContent = "Every year on " + monthName + " " + dom;
|
|
2663
|
-
|
|
2664
|
-
// Every month on the {date}th
|
|
2665
|
-
var monthlyEl = document.getElementById("sched-recurrence-monthly");
|
|
2666
|
-
if (monthlyEl) {
|
|
2667
|
-
var suffix = "th";
|
|
2668
|
-
if (dom === 1 || dom === 21 || dom === 31) suffix = "st";
|
|
2669
|
-
else if (dom === 2 || dom === 22) suffix = "nd";
|
|
2670
|
-
else if (dom === 3 || dom === 23) suffix = "rd";
|
|
2671
|
-
monthlyEl.textContent = "Every month on the " + dom + suffix;
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
function closeCreateModal() {
|
|
2676
|
-
if (createPopover) createPopover.classList.add("hidden");
|
|
2677
|
-
var dd = document.getElementById("sched-create-recurrence-dropdown");
|
|
2678
|
-
if (dd) dd.classList.add("hidden");
|
|
2679
|
-
var pal = document.getElementById("sched-create-color-palette");
|
|
2680
|
-
if (pal) pal.classList.add("hidden");
|
|
2681
|
-
var tl = document.getElementById("sched-create-task-list");
|
|
2682
|
-
if (tl) tl.classList.add("hidden");
|
|
2683
|
-
removePreview();
|
|
2684
|
-
createSelectedDate = null;
|
|
2685
|
-
createEditingRecId = null;
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
function openDeleteDialog(recId, eventDate, isOneOff) {
|
|
2689
|
-
var dialog = document.getElementById("sched-delete-dialog");
|
|
2690
|
-
if (!dialog) return;
|
|
2691
|
-
dialog.dataset.recId = recId;
|
|
2692
|
-
if (eventDate) {
|
|
2693
|
-
dialog.dataset.eventDate = eventDate.getFullYear() + "-" + pad(eventDate.getMonth() + 1) + "-" + pad(eventDate.getDate());
|
|
2694
|
-
} else {
|
|
2695
|
-
dialog.dataset.eventDate = "";
|
|
2696
|
-
}
|
|
2697
|
-
// Toggle between one-off and recurring UI
|
|
2698
|
-
var title = dialog.querySelector(".sched-delete-dialog-title");
|
|
2699
|
-
var body = dialog.querySelector(".sched-delete-dialog-body");
|
|
2700
|
-
var footer = dialog.querySelector(".sched-delete-dialog-footer");
|
|
2701
|
-
var cancelBtn = dialog.querySelector('[data-delete="cancel"]');
|
|
2702
|
-
dialog.dataset.oneOff = isOneOff ? "1" : "";
|
|
2703
|
-
if (isOneOff) {
|
|
2704
|
-
if (title) title.textContent = "Delete this event?";
|
|
2705
|
-
if (body) body.classList.add("hidden");
|
|
2706
|
-
if (cancelBtn) cancelBtn.textContent = "Cancel";
|
|
2707
|
-
// Add a "Delete" button next to cancel in footer
|
|
2708
|
-
var existingDel = footer ? footer.querySelector(".sched-delete-confirm-btn") : null;
|
|
2709
|
-
if (!existingDel && footer) {
|
|
2710
|
-
var delBtn = document.createElement("button");
|
|
2711
|
-
delBtn.className = "sched-delete-option danger sched-delete-confirm-btn";
|
|
2712
|
-
delBtn.dataset.delete = "all";
|
|
2713
|
-
delBtn.textContent = "Delete";
|
|
2714
|
-
footer.appendChild(delBtn);
|
|
2715
|
-
delBtn.addEventListener("click", function (e) {
|
|
2716
|
-
e.stopPropagation();
|
|
2717
|
-
var rid = dialog.dataset.recId;
|
|
2718
|
-
if (rid) send({ type: "loop_registry_remove", id: rid });
|
|
2719
|
-
closeDeleteDialog();
|
|
2720
|
-
});
|
|
2721
|
-
}
|
|
2722
|
-
if (existingDel) existingDel.classList.remove("hidden");
|
|
2723
|
-
} else {
|
|
2724
|
-
if (title) title.textContent = "Delete recurring event";
|
|
2725
|
-
if (body) body.classList.remove("hidden");
|
|
2726
|
-
if (cancelBtn) cancelBtn.textContent = "Cancel";
|
|
2727
|
-
var existingDel = footer ? footer.querySelector(".sched-delete-confirm-btn") : null;
|
|
2728
|
-
if (existingDel) existingDel.classList.add("hidden");
|
|
2729
|
-
}
|
|
2730
|
-
dialog.classList.remove("hidden");
|
|
2731
|
-
}
|
|
2732
|
-
|
|
2733
|
-
function closeDeleteDialog() {
|
|
2734
|
-
var dialog = document.getElementById("sched-delete-dialog");
|
|
2735
|
-
if (dialog) {
|
|
2736
|
-
dialog.classList.add("hidden");
|
|
2737
|
-
dialog.dataset.recId = "";
|
|
2738
|
-
dialog.dataset.eventDate = "";
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
|
|
2742
|
-
// Build an explicit list of values offset from a start value with a given step, wrapping at max
|
|
2743
|
-
function buildOffsetList(start, step, max) {
|
|
2744
|
-
var vals = [];
|
|
2745
|
-
var v = start % max;
|
|
2746
|
-
for (var i = 0; i < max; i += step) {
|
|
2747
|
-
vals.push(v);
|
|
2748
|
-
v = (v + step) % max;
|
|
2749
|
-
}
|
|
2750
|
-
vals.sort(function (a, b) { return a - b; });
|
|
2751
|
-
return vals.join(",");
|
|
2752
|
-
}
|
|
2753
|
-
|
|
2754
|
-
function buildCreateCron() {
|
|
2755
|
-
if (!createSelectedDate) return null;
|
|
2756
|
-
|
|
2757
|
-
var timeVal = document.getElementById("sched-create-time").value || "09:00";
|
|
2758
|
-
var timeParts = timeVal.split(":");
|
|
2759
|
-
var h = parseInt(timeParts[0], 10);
|
|
2760
|
-
var m = parseInt(timeParts[1], 10);
|
|
2761
|
-
|
|
2762
|
-
var dow = createSelectedDate.getDay();
|
|
2763
|
-
var dom = createSelectedDate.getDate();
|
|
2764
|
-
var month = createSelectedDate.getMonth() + 1;
|
|
2765
|
-
|
|
2766
|
-
// Determine interval minutes
|
|
2767
|
-
var intervalMins = 0;
|
|
2768
|
-
if (createInterval !== "none") {
|
|
2769
|
-
if (createInterval === "custom" && createIntervalCustom) {
|
|
2770
|
-
intervalMins = createIntervalCustom.unit === "hour"
|
|
2771
|
-
? createIntervalCustom.value * 60
|
|
2772
|
-
: createIntervalCustom.value;
|
|
2773
|
-
} else {
|
|
2774
|
-
intervalMins = parseInt(createInterval, 10) || 0;
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
|
-
// Interval only (no recurrence) = interval every day
|
|
2779
|
-
if (intervalMins > 0 && createRecurrence === "none") {
|
|
2780
|
-
if (intervalMins < 60) return buildOffsetList(m, intervalMins, 60) + " * * * *";
|
|
2781
|
-
var intHrs = Math.floor(intervalMins / 60);
|
|
2782
|
-
return String(m) + " " + buildOffsetList(h, intHrs, 24) + " * * *";
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
if (createRecurrence === "none" && intervalMins === 0) return null;
|
|
2786
|
-
|
|
2787
|
-
// Build minute/hour fields from interval or time
|
|
2788
|
-
var minField = String(m);
|
|
2789
|
-
var hourField = String(h);
|
|
2790
|
-
if (intervalMins > 0 && intervalMins < 60) {
|
|
2791
|
-
minField = buildOffsetList(m, intervalMins, 60);
|
|
2792
|
-
hourField = "*";
|
|
2793
|
-
} else if (intervalMins >= 60) {
|
|
2794
|
-
var intHrs2 = Math.floor(intervalMins / 60);
|
|
2795
|
-
minField = String(m);
|
|
2796
|
-
hourField = buildOffsetList(h, intHrs2, 24);
|
|
2797
|
-
}
|
|
2798
|
-
|
|
2799
|
-
if (createRecurrence === "daily") return minField + " " + hourField + " * * *";
|
|
2800
|
-
if (createRecurrence === "weekly") return minField + " " + hourField + " * * " + dow;
|
|
2801
|
-
if (createRecurrence === "biweekly") {
|
|
2802
|
-
var weekNum = Math.ceil(dom / 7);
|
|
2803
|
-
return minField + " " + hourField + " " + ((weekNum - 1) * 7 + 1) + "-" + (weekNum * 7) + " * " + dow;
|
|
2804
|
-
}
|
|
2805
|
-
if (createRecurrence === "yearly") return minField + " " + hourField + " " + dom + " " + month + " *";
|
|
2806
|
-
if (createRecurrence === "monthly") return minField + " " + hourField + " " + dom + " * *";
|
|
2807
|
-
if (createRecurrence === "weekdays") return minField + " " + hourField + " * * 1-5";
|
|
2808
|
-
|
|
2809
|
-
if (createRecurrence === "custom" && createCustomConfirmed) {
|
|
2810
|
-
return buildCustomCron(h, m);
|
|
2811
|
-
}
|
|
2812
|
-
|
|
2813
|
-
return null;
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
function updateEndDateLabel() {
|
|
2817
|
-
var label = document.getElementById("sched-custom-end-date-label");
|
|
2818
|
-
if (!label || !createEndDate) return;
|
|
2819
|
-
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
2820
|
-
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
2821
|
-
label.textContent = days[createEndDate.getDay()] + ", " + months[createEndDate.getMonth()] + " " + createEndDate.getDate();
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
function renderEndCalendar() {
|
|
2825
|
-
var grid = document.getElementById("sched-cal-grid");
|
|
2826
|
-
var titleEl = document.getElementById("sched-cal-title");
|
|
2827
|
-
if (!grid || !createEndCalMonth) return;
|
|
2828
|
-
|
|
2829
|
-
var year = createEndCalMonth.getFullYear();
|
|
2830
|
-
var month = createEndCalMonth.getMonth();
|
|
2831
|
-
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
2832
|
-
titleEl.textContent = months[month] + " " + year;
|
|
2833
|
-
|
|
2834
|
-
var firstDay = new Date(year, month, 1).getDay();
|
|
2835
|
-
var daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2836
|
-
var prevDays = new Date(year, month, 0).getDate();
|
|
2837
|
-
|
|
2838
|
-
var today = new Date();
|
|
2839
|
-
today.setHours(0, 0, 0, 0);
|
|
2840
|
-
|
|
2841
|
-
grid.innerHTML = "";
|
|
2842
|
-
|
|
2843
|
-
// Previous month filler
|
|
2844
|
-
for (var p = firstDay - 1; p >= 0; p--) {
|
|
2845
|
-
var d = prevDays - p;
|
|
2846
|
-
var btn = document.createElement("button");
|
|
2847
|
-
btn.className = "sched-cal-day other-month";
|
|
2848
|
-
btn.textContent = d;
|
|
2849
|
-
btn.type = "button";
|
|
2850
|
-
var prevDate = new Date(year, month - 1, d);
|
|
2851
|
-
(function (dt) {
|
|
2852
|
-
btn.addEventListener("click", function (e) {
|
|
2853
|
-
e.stopPropagation();
|
|
2854
|
-
createEndDate = dt;
|
|
2855
|
-
updateEndDateLabel();
|
|
2856
|
-
renderEndCalendar();
|
|
2857
|
-
});
|
|
2858
|
-
})(prevDate);
|
|
2859
|
-
grid.appendChild(btn);
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
// Current month
|
|
2863
|
-
for (var i = 1; i <= daysInMonth; i++) {
|
|
2864
|
-
var btn = document.createElement("button");
|
|
2865
|
-
btn.className = "sched-cal-day";
|
|
2866
|
-
btn.textContent = i;
|
|
2867
|
-
btn.type = "button";
|
|
2868
|
-
var cellDate = new Date(year, month, i);
|
|
2869
|
-
if (cellDate.getTime() === today.getTime()) btn.classList.add("today");
|
|
2870
|
-
if (createEndDate && cellDate.getFullYear() === createEndDate.getFullYear() && cellDate.getMonth() === createEndDate.getMonth() && cellDate.getDate() === createEndDate.getDate()) {
|
|
2871
|
-
btn.classList.add("selected");
|
|
2872
|
-
}
|
|
2873
|
-
(function (dt) {
|
|
2874
|
-
btn.addEventListener("click", function (e) {
|
|
2875
|
-
e.stopPropagation();
|
|
2876
|
-
createEndDate = dt;
|
|
2877
|
-
updateEndDateLabel();
|
|
2878
|
-
renderEndCalendar();
|
|
2879
|
-
});
|
|
2880
|
-
})(cellDate);
|
|
2881
|
-
grid.appendChild(btn);
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
// Next month filler
|
|
2885
|
-
var totalCells = firstDay + daysInMonth;
|
|
2886
|
-
var remaining = (7 - (totalCells % 7)) % 7;
|
|
2887
|
-
for (var n = 1; n <= remaining; n++) {
|
|
2888
|
-
var btn = document.createElement("button");
|
|
2889
|
-
btn.className = "sched-cal-day other-month";
|
|
2890
|
-
btn.textContent = n;
|
|
2891
|
-
btn.type = "button";
|
|
2892
|
-
var nextDate = new Date(year, month + 1, n);
|
|
2893
|
-
(function (dt) {
|
|
2894
|
-
btn.addEventListener("click", function (e) {
|
|
2895
|
-
e.stopPropagation();
|
|
2896
|
-
createEndDate = dt;
|
|
2897
|
-
updateEndDateLabel();
|
|
2898
|
-
renderEndCalendar();
|
|
2899
|
-
});
|
|
2900
|
-
})(nextDate);
|
|
2901
|
-
grid.appendChild(btn);
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
|
-
function buildCustomCron(h, m) {
|
|
2906
|
-
var interval = parseInt(document.getElementById("sched-custom-interval").value, 10) || 1;
|
|
2907
|
-
var unit = document.getElementById("sched-custom-unit").value;
|
|
2908
|
-
|
|
2909
|
-
if (unit === "minute") {
|
|
2910
|
-
return interval === 1 ? "*/1 * * * *" : buildOffsetList(m, interval, 60) + " * * * *";
|
|
2911
|
-
}
|
|
2912
|
-
if (unit === "hour") {
|
|
2913
|
-
return interval === 1 ? m + " */1 * * *" : m + " " + buildOffsetList(h, interval, 24) + " * * *";
|
|
2914
|
-
}
|
|
2915
|
-
if (unit === "day") {
|
|
2916
|
-
if (interval === 1) return m + " " + h + " * * *";
|
|
2917
|
-
return m + " " + h + " */" + interval + " * *";
|
|
2918
|
-
}
|
|
2919
|
-
|
|
2920
|
-
if (unit === "week") {
|
|
2921
|
-
var days = [];
|
|
2922
|
-
var btns = document.querySelectorAll("#sched-custom-dow-row .sched-dow-btn.active");
|
|
2923
|
-
for (var i = 0; i < btns.length; i++) days.push(btns[i].dataset.dow);
|
|
2924
|
-
if (days.length === 0) days.push(String(createSelectedDate ? createSelectedDate.getDay() : 0));
|
|
2925
|
-
return m + " " + h + " * * " + days.sort().join(",");
|
|
2926
|
-
}
|
|
2927
|
-
|
|
2928
|
-
if (unit === "month") {
|
|
2929
|
-
var dom = createSelectedDate ? createSelectedDate.getDate() : 1;
|
|
2930
|
-
if (interval === 1) return m + " " + h + " " + dom + " * *";
|
|
2931
|
-
return m + " " + h + " " + dom + " */" + interval + " *";
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
if (unit === "year") {
|
|
2935
|
-
var dom = createSelectedDate ? createSelectedDate.getDate() : 1;
|
|
2936
|
-
var month = createSelectedDate ? createSelectedDate.getMonth() + 1 : 1;
|
|
2937
|
-
return m + " " + h + " " + dom + " " + month + " *";
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
return null;
|
|
2941
|
-
}
|
|
2942
|
-
|
|
2943
|
-
function submitCreateSchedule() {
|
|
2944
|
-
var name = document.getElementById("sched-create-title").value.trim();
|
|
2945
|
-
if (!name) { document.getElementById("sched-create-title").focus(); return; }
|
|
2946
|
-
|
|
2947
|
-
var taskId = document.getElementById("sched-create-task").value || null;
|
|
2948
|
-
if (!taskId) {
|
|
2949
|
-
var taskBtn = document.getElementById("sched-create-task-btn");
|
|
2950
|
-
if (taskBtn) taskBtn.classList.add("invalid");
|
|
2951
|
-
return;
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
ctx.requireClayRalph(function () {
|
|
2955
|
-
var description = document.getElementById("sched-create-desc").value.trim();
|
|
2956
|
-
var datePicker = document.getElementById("sched-create-date-picker");
|
|
2957
|
-
var dateVal = datePicker ? datePicker.value : document.getElementById("sched-create-date").value;
|
|
2958
|
-
var timeVal = document.getElementById("sched-create-time").value || "09:00";
|
|
2959
|
-
|
|
2960
|
-
// Reject scheduling in the past
|
|
2961
|
-
if (dateVal && timeVal) {
|
|
2962
|
-
var dp = dateVal.split("-");
|
|
2963
|
-
var tp = timeVal.split(":");
|
|
2964
|
-
if (dp.length === 3 && tp.length >= 2) {
|
|
2965
|
-
var schedDate = new Date(
|
|
2966
|
-
parseInt(dp[0], 10), parseInt(dp[1], 10) - 1, parseInt(dp[2], 10),
|
|
2967
|
-
parseInt(tp[0], 10), parseInt(tp[1], 10), 0
|
|
2968
|
-
);
|
|
2969
|
-
if (schedDate < new Date()) {
|
|
2970
|
-
showToast("Cannot schedule a task in the past", "error");
|
|
2971
|
-
return;
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
}
|
|
2975
|
-
|
|
2976
|
-
var cron = buildCreateCron();
|
|
2977
|
-
|
|
2978
|
-
// Build recurrence end info
|
|
2979
|
-
var recurrenceEnd = null;
|
|
2980
|
-
// Interval-only (no recurrence): limit to the scheduled date only
|
|
2981
|
-
if (cron && createRecurrence === "none" && createInterval !== "none" && dateVal) {
|
|
2982
|
-
recurrenceEnd = { type: "until", date: dateVal };
|
|
2983
|
-
}
|
|
2984
|
-
if (cron && createRecurrence === "custom" && createCustomConfirmed) {
|
|
2985
|
-
if (createEndType === "until" && createEndDate) {
|
|
2986
|
-
var ey = createEndDate.getFullYear();
|
|
2987
|
-
var em = String(createEndDate.getMonth() + 1).padStart(2, "0");
|
|
2988
|
-
var ed = String(createEndDate.getDate()).padStart(2, "0");
|
|
2989
|
-
recurrenceEnd = { type: "until", date: ey + "-" + em + "-" + ed };
|
|
2990
|
-
} else if (createEndType === "after" && createEndAfter > 0) {
|
|
2991
|
-
recurrenceEnd = { type: "after", count: createEndAfter };
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
// Build interval end info
|
|
2996
|
-
var intervalEnd = null;
|
|
2997
|
-
if (createInterval !== "none") {
|
|
2998
|
-
if (createIntervalEnd === "after" && createIntervalEndAfter > 0) {
|
|
2999
|
-
intervalEnd = { type: "after", count: createIntervalEndAfter };
|
|
3000
|
-
} else if (createIntervalEnd === "until" && createIntervalEndTime) {
|
|
3001
|
-
intervalEnd = { type: "until", time: createIntervalEndTime };
|
|
3002
|
-
}
|
|
3003
|
-
// "allday" = null (no limit)
|
|
3004
|
-
}
|
|
3005
|
-
|
|
3006
|
-
var skipRunningEl = document.getElementById("sched-skip-running");
|
|
3007
|
-
var skipIfRunning = skipRunningEl ? skipRunningEl.checked : true;
|
|
3008
|
-
|
|
3009
|
-
var activeRunMode = createPopover.querySelector(".sched-run-mode-btn.active");
|
|
3010
|
-
var runMode = activeRunMode ? activeRunMode.dataset.mode : "single";
|
|
3011
|
-
var maxIterations = 1;
|
|
3012
|
-
if (runMode === "multi") {
|
|
3013
|
-
var iterInput = document.getElementById("sched-create-iterations");
|
|
3014
|
-
maxIterations = iterInput ? (parseInt(iterInput.value, 10) || 3) : 3;
|
|
3015
|
-
if (maxIterations < 2) maxIterations = 2;
|
|
3016
|
-
if (maxIterations > 100) maxIterations = 100;
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
if (createEditingRecId) {
|
|
3020
|
-
send({
|
|
3021
|
-
type: "loop_registry_update",
|
|
3022
|
-
id: createEditingRecId,
|
|
3023
|
-
data: {
|
|
3024
|
-
name: name,
|
|
3025
|
-
description: description,
|
|
3026
|
-
date: dateVal,
|
|
3027
|
-
time: timeVal,
|
|
3028
|
-
allDay: false,
|
|
3029
|
-
cron: cron,
|
|
3030
|
-
enabled: cron ? true : false,
|
|
3031
|
-
color: createColor,
|
|
3032
|
-
recurrenceEnd: recurrenceEnd,
|
|
3033
|
-
intervalEnd: intervalEnd,
|
|
3034
|
-
maxIterations: maxIterations,
|
|
3035
|
-
skipIfRunning: skipIfRunning,
|
|
3036
|
-
},
|
|
3037
|
-
});
|
|
3038
|
-
} else {
|
|
3039
|
-
send({
|
|
3040
|
-
type: "schedule_create",
|
|
3041
|
-
data: {
|
|
3042
|
-
name: name,
|
|
3043
|
-
taskId: taskId,
|
|
3044
|
-
description: description,
|
|
3045
|
-
date: dateVal,
|
|
3046
|
-
time: timeVal,
|
|
3047
|
-
allDay: false,
|
|
3048
|
-
cron: cron,
|
|
3049
|
-
enabled: cron ? true : false,
|
|
3050
|
-
color: createColor,
|
|
3051
|
-
recurrenceEnd: recurrenceEnd,
|
|
3052
|
-
intervalEnd: intervalEnd,
|
|
3053
|
-
maxIterations: maxIterations,
|
|
3054
|
-
skipIfRunning: skipIfRunning,
|
|
3055
|
-
},
|
|
3056
|
-
});
|
|
3057
|
-
}
|
|
3058
|
-
|
|
3059
|
-
closeCreateModal();
|
|
3060
|
-
});
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
// --- Cron parser (client-side) ---
|
|
3064
|
-
|
|
3065
|
-
function parseCronSimple(expr) {
|
|
3066
|
-
if (!expr) return null;
|
|
3067
|
-
var fields = expr.trim().split(/\s+/);
|
|
3068
|
-
if (fields.length !== 5) return null;
|
|
3069
|
-
return {
|
|
3070
|
-
minutes: parseField(fields[0], 0, 59),
|
|
3071
|
-
hours: parseField(fields[1], 0, 23),
|
|
3072
|
-
daysOfMonth: parseField(fields[2], 1, 31),
|
|
3073
|
-
months: parseField(fields[3], 1, 12),
|
|
3074
|
-
daysOfWeek: parseField(fields[4], 0, 6),
|
|
3075
|
-
};
|
|
3076
|
-
}
|
|
3077
|
-
|
|
3078
|
-
function parseField(field, min, max) {
|
|
3079
|
-
var values = [];
|
|
3080
|
-
var parts = field.split(",");
|
|
3081
|
-
for (var i = 0; i < parts.length; i++) {
|
|
3082
|
-
var part = parts[i].trim();
|
|
3083
|
-
if (part.indexOf("/") !== -1) {
|
|
3084
|
-
var sp = part.split("/");
|
|
3085
|
-
var step = parseInt(sp[1], 10);
|
|
3086
|
-
var rMin = min, rMax = max;
|
|
3087
|
-
if (sp[0] !== "*") { var rp = sp[0].split("-"); rMin = parseInt(rp[0], 10); rMax = rp.length > 1 ? parseInt(rp[1], 10) : rMin; }
|
|
3088
|
-
for (var v = rMin; v <= rMax; v += step) values.push(v);
|
|
3089
|
-
} else if (part === "*") {
|
|
3090
|
-
for (var v = min; v <= max; v++) values.push(v);
|
|
3091
|
-
} else if (part.indexOf("-") !== -1) {
|
|
3092
|
-
var rp = part.split("-");
|
|
3093
|
-
for (var v = parseInt(rp[0], 10); v <= parseInt(rp[1], 10); v++) values.push(v);
|
|
3094
|
-
} else {
|
|
3095
|
-
values.push(parseInt(part, 10));
|
|
3096
|
-
}
|
|
3097
|
-
}
|
|
3098
|
-
return values;
|
|
3099
|
-
}
|
|
3100
|
-
|
|
3101
1580
|
// --- Utility ---
|
|
3102
1581
|
|
|
3103
1582
|
function getISOWeekNumber(date) {
|