Files
luxtools-plugin/js/event-popup.js
2026-03-20 07:56:41 +01:00

1171 lines
36 KiB
JavaScript

/* global window, document, jQuery */
/**
* Event Popup, Day Popup, Event CRUD, and Maintenance Task Handling
*
* - Clicking an event item with data-luxtools-event="1" opens a detail popup.
* - Clicking empty space in a calendar day cell opens a day popup listing all events.
* - Day popup includes a "Create Event" action for authenticated users.
* - Event popup includes "Edit" and "Delete" actions for authenticated users.
* - Clicking a maintenance task action button sends an AJAX request to
* complete/reopen the task.
*/
(function () {
"use strict";
var Luxtools = window.Luxtools || (window.Luxtools = {});
// Temporary storage for form data when showing the recurring edit scope dialog
var _pendingEditFormData = null;
// ============================================================
// Shared helpers
// ============================================================
function escapeHtml(text) {
var div = document.createElement("div");
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}
function pad2(value) {
return String(value).padStart(2, "0");
}
function formatDate(isoStr) {
if (!isoStr) return "";
var d = new Date(isoStr);
if (isNaN(d.getTime())) return isoStr;
return (
pad2(d.getDate()) + "." + pad2(d.getMonth() + 1) + "." + d.getFullYear()
);
}
function formatDateTime(isoStr) {
if (!isoStr) return "";
var d = new Date(isoStr);
if (isNaN(d.getTime())) return isoStr;
return (
formatDate(isoStr) + " " + pad2(d.getHours()) + ":" + pad2(d.getMinutes())
);
}
function formatTimeOnly(isoStr) {
if (!isoStr) return "";
var d = new Date(isoStr);
if (isNaN(d.getTime())) return isoStr;
return pad2(d.getHours()) + ":" + pad2(d.getMinutes());
}
function formatEventListTime(startIso, fallbackTime) {
var formatted = formatTimeOnly(startIso);
if (!formatted || formatted === startIso) {
return fallbackTime || "";
}
return formatted;
}
function isSameMoment(left, right) {
if (!left || !right) return false;
return left === right;
}
function getAjaxUrl() {
return (window.DOKU_BASE || "/") + "lib/exe/ajax.php";
}
function getSecurityToken(el) {
// Try element hierarchy
if (el) {
var container = el.closest ? el.closest("[data-luxtools-sectok]") : null;
if (container) {
var tok = container.getAttribute("data-luxtools-sectok");
if (tok) return tok;
}
}
if (window.JSINFO && window.JSINFO.sectok)
return String(window.JSINFO.sectok);
var input = document.querySelector(
'input[name="sectok"], input[name="securitytoken"]',
);
if (input && input.value) return String(input.value);
return "";
}
function isAuthenticated() {
// Check if user is logged in via JSINFO
if (window.JSINFO && window.JSINFO.isadmin) return true;
if (window.JSINFO && window.JSINFO.id !== undefined) {
// Check if user info exists (logged in users have a userinfo)
var userinfo = document.querySelector(".user");
if (userinfo) return true;
// Alternative: check for logout form
var logoutLink = document.querySelector(
'a[href*="do=logout"], .action.logout',
);
if (logoutLink) return true;
}
return false;
}
function showNotification(message, type) {
if (typeof window.msg === "function") {
var level = type === "error" ? -1 : type === "warning" ? 0 : 1;
window.msg(message, level);
return;
}
var notif = document.createElement("div");
notif.className = "luxtools-notification luxtools-notification-" + type;
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(function () {
if (notif.parentNode) notif.parentNode.removeChild(notif);
}, 5000);
}
// Lazy reference to the shared dialog infrastructure (dialog.js).
// Accessed via function to handle any script-loading order variation.
function getDialog() {
return Luxtools.Dialog;
}
// ============================================================
// Event Popup (single event detail)
// ============================================================
var EventPopup = (function () {
/**
* Open event detail popup.
* @param {Element} el - Element with data-event-* attributes
* @param {object} [opts] - Options
* @param {boolean} [opts.hideDatetime] - Hide the date/time field (when opened from day context)
*/
function open(el, opts) {
opts = opts || {};
var summary = el.getAttribute("data-event-summary") || "";
var start = el.getAttribute("data-event-start") || "";
var end = el.getAttribute("data-event-end") || "";
var location = el.getAttribute("data-event-location") || "";
var description = el.getAttribute("data-event-description") || "";
var allDay = el.getAttribute("data-event-allday") === "1";
var slot = el.getAttribute("data-event-slot") || "";
var uid = el.getAttribute("data-event-uid") || "";
var recurrence = el.getAttribute("data-event-recurrence") || "";
var dateIso = el.getAttribute("data-event-date") || "";
var html = '<div class="luxtools-dialog-content">';
html +=
'<button type="button" class="luxtools-dialog-close" aria-label="Close">&times;</button>';
html +=
'<h3 class="luxtools-dialog-title">' + escapeHtml(summary) + "</h3>";
// Date/time - hide when opened from a day context
if (!opts.hideDatetime) {
html += '<div class="luxtools-dialog-field">';
if (allDay) {
html += "<strong>Date:</strong> " + formatDate(start);
if (end && !isSameMoment(start, end)) {
html += " &ndash; " + formatDate(end);
}
} else {
html += "<strong>Time:</strong> " + formatDateTime(start);
if (end && !isSameMoment(start, end)) {
html += " &ndash; " + formatDateTime(end);
}
}
html += "</div>";
} else if (!allDay && start) {
// In day context, show only time (not date)
html += '<div class="luxtools-dialog-field">';
html += "<strong>Time:</strong> " + formatTimeOnly(start);
if (end && !isSameMoment(start, end)) {
html += " &ndash; " + formatTimeOnly(end);
}
html += "</div>";
}
if (location) {
html +=
'<div class="luxtools-dialog-field"><strong>Location:</strong> ' +
escapeHtml(location) +
"</div>";
}
if (description) {
html +=
'<div class="luxtools-dialog-field luxtools-event-popup-description">' +
"<strong>Description:</strong><br>" +
escapeHtml(description).replace(/\n/g, "<br>") +
"</div>";
}
if (slot) {
html +=
'<div class="luxtools-event-popup-slot"><em>' +
escapeHtml(slot) +
"</em></div>";
}
// Edit/Delete actions for authenticated users with a UID
if (uid && isAuthenticated()) {
html += '<div class="luxtools-dialog-actions">';
html +=
'<button type="button" class="button luxtools-event-edit-btn"' +
' data-uid="' +
escapeHtml(uid) +
'"' +
' data-slot="' +
escapeHtml(slot) +
'"' +
' data-recurrence="' +
escapeHtml(recurrence) +
'"' +
' data-date="' +
escapeHtml(dateIso) +
'"' +
' data-summary="' +
escapeHtml(summary) +
'"' +
' data-start="' +
escapeHtml(start) +
'"' +
' data-end="' +
escapeHtml(end) +
'"' +
' data-location="' +
escapeHtml(location) +
'"' +
' data-description="' +
escapeHtml(description) +
'"' +
' data-allday="' +
(allDay ? "1" : "0") +
'"' +
">Edit</button> ";
html +=
'<button type="button" class="button luxtools-event-delete-btn"' +
' data-uid="' +
escapeHtml(uid) +
'"' +
' data-slot="' +
escapeHtml(slot) +
'"' +
' data-recurrence="' +
escapeHtml(recurrence) +
'"' +
' data-date="' +
escapeHtml(dateIso) +
'"' +
">Delete</button>";
html += "</div>";
}
html += "</div>";
getDialog().show(html);
}
function close() {
getDialog().close();
}
return { open: open, close: close };
})();
// ============================================================
// Day Popup (list events for a specific day)
// ============================================================
var DayPopup = (function () {
function open(dayCell) {
var dateIso = dayCell.getAttribute("data-luxtools-date") || "";
if (!dateIso) return;
var events = [];
var rawJson = dayCell.getAttribute("data-luxtools-day-events");
if (rawJson) {
try {
events = JSON.parse(rawJson);
} catch (e) {
events = [];
}
}
var html = '<div class="luxtools-dialog-content">';
html +=
'<button type="button" class="luxtools-dialog-close" aria-label="Close">&times;</button>';
html +=
'<h3 class="luxtools-dialog-title">' +
escapeHtml(formatDate(dateIso + "T00:00:00")) +
"</h3>";
if (events.length === 0) {
html +=
'<div class="luxtools-dialog-field luxtools-day-popup-empty">No events</div>';
} else {
html += '<ul class="luxtools-day-popup-events">';
for (var i = 0; i < events.length; i++) {
var ev = events[i];
var attrs =
' data-luxtools-event="1"' +
' data-event-summary="' +
escapeHtml(ev.summary || "") +
'"' +
' data-event-start="' +
escapeHtml(ev.start || "") +
'"' +
' data-event-end="' +
escapeHtml(ev.end || "") +
'"' +
' data-event-location="' +
escapeHtml(ev.location || "") +
'"' +
' data-event-description="' +
escapeHtml(ev.description || "") +
'"' +
' data-event-allday="' +
(ev.allDay ? "1" : "0") +
'"' +
' data-event-slot="' +
escapeHtml(ev.slot || "") +
'"' +
' data-event-uid="' +
escapeHtml(ev.uid || "") +
'"' +
' data-event-recurrence="' +
escapeHtml(ev.recurrence || "") +
'"' +
' data-event-date="' +
escapeHtml(ev.date || dateIso) +
'"';
var timeLabel = "";
var timeStr = "";
if (!ev.allDay) {
timeLabel = formatEventListTime(ev.start || "", ev.time || "");
}
if (timeLabel) {
timeStr =
'<span class="luxtools-event-time">' +
escapeHtml(timeLabel) +
"</span> - ";
}
html +=
'<li class="luxtools-day-popup-event-item"' +
attrs +
">" +
timeStr +
'<span class="luxtools-event-summary">' +
escapeHtml(ev.summary || "") +
"</span>" +
"</li>";
}
html += "</ul>";
}
// Create Event action for authenticated users
if (isAuthenticated()) {
html += '<div class="luxtools-dialog-actions">';
html +=
'<button type="button" class="button luxtools-event-create-btn" data-date="' +
escapeHtml(dateIso) +
'">Create Event</button>';
html += "</div>";
}
html += "</div>";
getDialog().show(html);
}
return { open: open };
})();
// ============================================================
// Event Form (create/edit)
// ============================================================
var EventForm = (function () {
var _calendarSlots = null;
function loadSlots(callback) {
if (_calendarSlots) {
callback(_calendarSlots);
return;
}
var xhr = new XMLHttpRequest();
xhr.open("GET", getAjaxUrl() + "?call=luxtools_calendar_slots", true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.onload = function () {
try {
var result = JSON.parse(xhr.responseText);
if (result.ok && result.slots) {
_calendarSlots = result.slots;
} else {
_calendarSlots = [];
}
} catch (e) {
_calendarSlots = [];
}
callback(_calendarSlots);
};
xhr.onerror = function () {
_calendarSlots = [];
callback(_calendarSlots);
};
xhr.send();
}
function openCreate(dateIso) {
loadSlots(function (slots) {
renderForm(
{
mode: "create",
date: dateIso,
summary: "",
startTime: "",
endTime: "",
location: "",
description: "",
allDay: true,
slot: slots.length > 0 ? slots[0].key : "general",
},
slots,
);
});
}
function openEdit(data) {
loadSlots(function (slots) {
// Parse start/end times
var startTime = "";
var endTime = "";
if (!data.allDay && data.start) {
var sd = new Date(data.start);
if (!isNaN(sd.getTime()))
startTime = pad2(sd.getHours()) + ":" + pad2(sd.getMinutes());
}
if (!data.allDay && data.end) {
var ed = new Date(data.end);
if (!isNaN(ed.getTime()))
endTime = pad2(ed.getHours()) + ":" + pad2(ed.getMinutes());
}
renderForm(
{
mode: "edit",
uid: data.uid || "",
recurrence: data.recurrence || "",
date: data.date || "",
summary: data.summary || "",
startTime: startTime,
endTime: endTime,
location: data.location || "",
description: data.description || "",
allDay: data.allDay,
slot: data.slot || "general",
},
slots,
);
});
}
function renderForm(data, slots) {
var isEdit = data.mode === "edit";
var title = isEdit ? "Edit Event" : "Create Event";
var html = '<div class="luxtools-dialog-content luxtools-event-form">';
html +=
'<button type="button" class="luxtools-dialog-close" aria-label="Close">&times;</button>';
html +=
'<h3 class="luxtools-dialog-title">' + escapeHtml(title) + "</h3>";
html += '<div class="luxtools-event-form-field">';
html +=
'<label>Summary<br><input type="text" class="edit luxtools-form-summary" value="' +
escapeHtml(data.summary) +
'" /></label>';
html += "</div>";
html += '<div class="luxtools-event-form-field">';
html +=
'<label>Date<br><input type="date" class="edit luxtools-form-date" value="' +
escapeHtml(data.date) +
'" /></label>';
html += "</div>";
html += '<div class="luxtools-event-form-field">';
html +=
'<label><input type="checkbox" class="luxtools-form-allday"' +
(data.allDay ? " checked" : "") +
" /> All day</label>";
html += "</div>";
html +=
'<div class="luxtools-event-form-time-fields"' +
(data.allDay ? ' style="display:none"' : "") +
">";
html += '<div class="luxtools-event-form-field">';
html +=
'<label>Start time<br><input type="time" step="60" class="edit luxtools-form-start-time" value="' +
escapeHtml(data.startTime) +
'" /></label>';
html += "</div>";
html += '<div class="luxtools-event-form-field">';
html +=
'<label>End time<br><input type="time" step="60" class="edit luxtools-form-end-time" value="' +
escapeHtml(data.endTime) +
'" /></label>';
html += "</div>";
html += "</div>";
html += '<div class="luxtools-event-form-field">';
html +=
'<label>Location<br><input type="text" class="edit luxtools-form-location" value="' +
escapeHtml(data.location) +
'" /></label>';
html += "</div>";
html += '<div class="luxtools-event-form-field">';
html +=
'<label>Description<br><textarea class="edit luxtools-form-description" rows="3">' +
escapeHtml(data.description) +
"</textarea></label>";
html += "</div>";
html += '<div class="luxtools-event-form-field">';
html += '<label>Calendar<br><select class="edit luxtools-form-slot">';
for (var i = 0; i < slots.length; i++) {
var sel = slots[i].key === data.slot ? " selected" : "";
html +=
'<option value="' +
escapeHtml(slots[i].key) +
'"' +
sel +
">" +
escapeHtml(slots[i].label) +
"</option>";
}
html += "</select></label>";
html += "</div>";
html += '<div class="luxtools-dialog-actions">';
html +=
'<button type="button" class="button luxtools-event-form-save"' +
' data-mode="' +
escapeHtml(data.mode) +
'"' +
(isEdit ? ' data-uid="' + escapeHtml(data.uid) + '"' : "") +
(isEdit
? ' data-recurrence="' + escapeHtml(data.recurrence) + '"'
: "") +
">Save</button> ";
html +=
'<button type="button" class="button luxtools-event-form-cancel">Cancel</button>';
html += "</div>";
html += "</div>";
getDialog().show(html);
// Wire up all-day checkbox toggle
var popup = getDialog().getContainer();
var allDayCheckbox = popup.querySelector(".luxtools-form-allday");
var timeFields = popup.querySelector(".luxtools-event-form-time-fields");
if (allDayCheckbox && timeFields) {
allDayCheckbox.addEventListener("change", function () {
timeFields.style.display = allDayCheckbox.checked ? "none" : "";
});
}
}
function collectFormData() {
var popup = getDialog().getContainer();
return {
summary:
(popup.querySelector(".luxtools-form-summary") || {}).value || "",
date: (popup.querySelector(".luxtools-form-date") || {}).value || "",
allDay: !!(popup.querySelector(".luxtools-form-allday") || {}).checked,
startTime:
(popup.querySelector(".luxtools-form-start-time") || {}).value || "",
endTime:
(popup.querySelector(".luxtools-form-end-time") || {}).value || "",
location:
(popup.querySelector(".luxtools-form-location") || {}).value || "",
description:
(popup.querySelector(".luxtools-form-description") || {}).value || "",
slot:
(popup.querySelector(".luxtools-form-slot") || {}).value || "general",
};
}
function save(saveBtn) {
var mode = saveBtn.getAttribute("data-mode");
var formData = collectFormData();
if (!formData.summary.trim()) {
showNotification("Summary is required", "error");
return;
}
if (!formData.date) {
showNotification("Date is required", "error");
return;
}
// For recurring event edits, ask about scope first
if (mode === "edit") {
var recurrence = saveBtn.getAttribute("data-recurrence") || "";
if (recurrence) {
showRecurrenceEditScopeDialog(saveBtn, formData);
return;
}
}
submitSave(saveBtn, formData, "all");
}
function showRecurrenceEditScopeDialog(saveBtn, formData) {
var uid = saveBtn.getAttribute("data-uid") || "";
var recurrence = saveBtn.getAttribute("data-recurrence") || "";
var html = '<div class="luxtools-dialog-content">';
html +=
'<button type="button" class="luxtools-dialog-close" aria-label="Close">&times;</button>';
html += '<h3 class="luxtools-dialog-title">Edit Recurring Event</h3>';
html += "<p>This is a recurring event. What would you like to edit?</p>";
html +=
'<div class="luxtools-dialog-actions luxtools-recurrence-actions">';
var baseAttrs =
' data-uid="' +
escapeHtml(uid) +
'"' +
' data-recurrence="' +
escapeHtml(recurrence) +
'"' +
' data-mode="edit"';
html +=
'<button type="button" class="button luxtools-event-confirm-edit-scope"' +
baseAttrs +
' data-scope="this">This occurrence</button> ';
html +=
'<button type="button" class="button luxtools-event-confirm-edit-scope"' +
baseAttrs +
' data-scope="future">This and future</button> ';
html +=
'<button type="button" class="button luxtools-event-confirm-edit-scope"' +
baseAttrs +
' data-scope="all">All occurrences</button> ';
html +=
'<button type="button" class="button luxtools-event-form-cancel">Cancel</button>';
html += "</div></div>";
// Store formData on the global scope so the scope button handler can access it
_pendingEditFormData = formData;
getDialog().show(html);
}
function submitSave(saveBtn, formData, scope) {
saveBtn.disabled = true;
saveBtn.textContent = "Saving...";
var mode = saveBtn.getAttribute("data-mode") || "create";
var params =
"call=luxtools_calendar_event" +
"&action=" +
encodeURIComponent(mode === "edit" ? "edit" : "create") +
"&summary=" +
encodeURIComponent(formData.summary) +
"&date=" +
encodeURIComponent(formData.date) +
"&allday=" +
(formData.allDay ? "1" : "0") +
"&start_time=" +
encodeURIComponent(formData.startTime) +
"&end_time=" +
encodeURIComponent(formData.endTime) +
"&location=" +
encodeURIComponent(formData.location) +
"&description=" +
encodeURIComponent(formData.description) +
"&slot=" +
encodeURIComponent(formData.slot) +
"&sectok=" +
encodeURIComponent(getSecurityToken(saveBtn));
if (mode === "edit") {
params +=
"&uid=" + encodeURIComponent(saveBtn.getAttribute("data-uid") || "");
params +=
"&recurrence=" +
encodeURIComponent(saveBtn.getAttribute("data-recurrence") || "");
params += "&scope=" + encodeURIComponent(scope);
}
var xhr = new XMLHttpRequest();
xhr.open("POST", getAjaxUrl(), true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onload = function () {
try {
var result = JSON.parse(xhr.responseText);
if (result.ok) {
getDialog().close();
showNotification(result.message || "Saved", "success");
window.location.reload();
} else {
showNotification(result.error || "Save failed", "error");
saveBtn.disabled = false;
saveBtn.textContent = "Save";
}
} catch (e) {
showNotification("Invalid response", "error");
saveBtn.disabled = false;
saveBtn.textContent = "Save";
}
};
xhr.onerror = function () {
showNotification("Network error", "error");
saveBtn.disabled = false;
saveBtn.textContent = "Save";
};
xhr.send(params);
}
return {
openCreate: openCreate,
openEdit: openEdit,
save: save,
submitSave: submitSave,
};
})();
// ============================================================
// Event Deletion
// ============================================================
var EventDelete = (function () {
function confirmDelete(btn) {
var uid = btn.getAttribute("data-uid") || "";
var slot = btn.getAttribute("data-slot") || "";
var recurrence = btn.getAttribute("data-recurrence") || "";
var dateIso = btn.getAttribute("data-date") || "";
if (!uid) return;
// For recurring events, ask about scope
if (recurrence) {
showRecurrenceDeleteDialog(uid, slot, recurrence, dateIso);
return;
}
// Simple confirmation
var html = '<div class="luxtools-dialog-content">';
html +=
'<button type="button" class="luxtools-dialog-close" aria-label="Close">&times;</button>';
html += '<h3 class="luxtools-dialog-title">Delete Event</h3>';
html += "<p>Are you sure you want to delete this event?</p>";
html += '<div class="luxtools-dialog-actions">';
html +=
'<button type="button" class="button luxtools-event-confirm-delete"' +
' data-uid="' +
escapeHtml(uid) +
'"' +
' data-slot="' +
escapeHtml(slot) +
'"' +
' data-recurrence="' +
escapeHtml(recurrence) +
'"' +
' data-date="' +
escapeHtml(dateIso) +
'"' +
' data-scope="all"' +
">Delete</button> ";
html +=
'<button type="button" class="button luxtools-event-form-cancel">Cancel</button>';
html += "</div></div>";
getDialog().show(html);
}
function showRecurrenceDeleteDialog(uid, slot, recurrence, dateIso) {
var html = '<div class="luxtools-dialog-content">';
html +=
'<button type="button" class="luxtools-dialog-close" aria-label="Close">&times;</button>';
html += '<h3 class="luxtools-dialog-title">Delete Recurring Event</h3>';
html +=
"<p>This is a recurring event. What would you like to delete?</p>";
html +=
'<div class="luxtools-dialog-actions luxtools-recurrence-actions">';
var baseAttrs =
' data-uid="' +
escapeHtml(uid) +
'"' +
' data-slot="' +
escapeHtml(slot) +
'"' +
' data-recurrence="' +
escapeHtml(recurrence) +
'"' +
' data-date="' +
escapeHtml(dateIso) +
'"';
html +=
'<button type="button" class="button luxtools-event-confirm-delete"' +
baseAttrs +
' data-scope="this">This occurrence</button> ';
html +=
'<button type="button" class="button luxtools-event-confirm-delete"' +
baseAttrs +
' data-scope="future">This and future</button> ';
html +=
'<button type="button" class="button luxtools-event-confirm-delete"' +
baseAttrs +
' data-scope="all">All occurrences</button> ';
html +=
'<button type="button" class="button luxtools-event-form-cancel">Cancel</button>';
html += "</div></div>";
getDialog().show(html);
}
function executeDelete(btn) {
var uid = btn.getAttribute("data-uid") || "";
var slot = btn.getAttribute("data-slot") || "";
var recurrence = btn.getAttribute("data-recurrence") || "";
var dateIso = btn.getAttribute("data-date") || "";
var scope = btn.getAttribute("data-scope") || "all";
btn.disabled = true;
btn.textContent = "Deleting...";
var params =
"call=luxtools_calendar_event" +
"&action=delete" +
"&uid=" +
encodeURIComponent(uid) +
"&slot=" +
encodeURIComponent(slot) +
"&recurrence=" +
encodeURIComponent(recurrence) +
"&date=" +
encodeURIComponent(dateIso) +
"&scope=" +
encodeURIComponent(scope) +
"&sectok=" +
encodeURIComponent(getSecurityToken(btn));
var xhr = new XMLHttpRequest();
xhr.open("POST", getAjaxUrl(), true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onload = function () {
try {
var result = JSON.parse(xhr.responseText);
if (result.ok) {
getDialog().close();
showNotification(result.message || "Deleted", "success");
window.location.reload();
} else {
showNotification(result.error || "Delete failed", "error");
btn.disabled = false;
btn.textContent =
btn.getAttribute("data-scope") === "all"
? "Delete"
: btn.textContent;
}
} catch (e) {
showNotification("Invalid response", "error");
btn.disabled = false;
}
};
xhr.onerror = function () {
showNotification("Network error", "error");
btn.disabled = false;
};
xhr.send(params);
}
return { confirmDelete: confirmDelete, executeDelete: executeDelete };
})();
// ============================================================
// Maintenance Task Actions
// ============================================================
var MaintenanceTasks = (function () {
function handleAction(button) {
var action = button.getAttribute("data-action");
if (!action) return;
var item = button.closest("[data-task-uid]");
if (!item) item = button.closest("[data-uid]");
if (!item) return;
var uid =
item.getAttribute("data-task-uid") ||
item.getAttribute("data-uid") ||
"";
var date =
item.getAttribute("data-task-date") ||
item.getAttribute("data-date") ||
"";
var recurrence =
item.getAttribute("data-task-recurrence") ||
item.getAttribute("data-recurrence") ||
"";
if (!uid || !date) return;
var ajaxUrl = getAjaxUrl();
var sectok = getSecurityToken(item);
button.disabled = true;
button.textContent = "...";
var params =
"call=luxtools_maintenance_task" +
"&action=" +
encodeURIComponent(action) +
"&uid=" +
encodeURIComponent(uid) +
"&date=" +
encodeURIComponent(date) +
"&recurrence=" +
encodeURIComponent(recurrence) +
"&sectok=" +
encodeURIComponent(sectok);
var xhr = new XMLHttpRequest();
xhr.open("POST", ajaxUrl, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onload = function () {
var result;
try {
result = JSON.parse(xhr.responseText);
} catch (e) {
result = { ok: false, error: "Invalid response" };
}
if (result.ok) {
if (action === "complete") {
item.classList.add("luxtools-task-completed");
button.textContent = "Reopen";
button.setAttribute("data-action", "reopen");
} else {
item.classList.remove("luxtools-task-completed");
item.style.opacity = "1";
button.textContent = "Complete";
button.setAttribute("data-action", "complete");
}
button.disabled = false;
if (result.remoteOk === false && result.remoteError) {
showNotification(result.remoteError, "warning");
}
} else {
showNotification(result.error || "Action failed", "error");
button.textContent = action === "complete" ? "Complete" : "Reopen";
button.disabled = false;
}
};
xhr.onerror = function () {
showNotification("Network error", "error");
button.textContent = action === "complete" ? "Complete" : "Reopen";
button.disabled = false;
};
xhr.send(params);
}
return { handleAction: handleAction };
})();
// ============================================================
// Event Delegation
// ============================================================
document.addEventListener(
"click",
function (e) {
var target = e.target;
// Maintenance task action buttons (day pages)
if (
target.classList &&
target.classList.contains("luxtools-task-action")
) {
e.preventDefault();
MaintenanceTasks.handleAction(target);
return;
}
// Maintenance task complete buttons (syntax plugin list)
if (
target.classList &&
target.classList.contains("luxtools-task-complete-btn")
) {
e.preventDefault();
MaintenanceTasks.handleAction(target);
return;
}
// Event form save
if (
target.classList &&
target.classList.contains("luxtools-event-form-save")
) {
e.preventDefault();
EventForm.save(target);
return;
}
// Event form cancel
if (
target.classList &&
target.classList.contains("luxtools-event-form-cancel")
) {
e.preventDefault();
getDialog().close();
return;
}
// Event create button (from day popup)
if (
target.classList &&
target.classList.contains("luxtools-event-create-btn")
) {
e.preventDefault();
EventForm.openCreate(target.getAttribute("data-date") || "");
return;
}
// Event edit button
if (
target.classList &&
target.classList.contains("luxtools-event-edit-btn")
) {
e.preventDefault();
EventForm.openEdit({
uid: target.getAttribute("data-uid") || "",
slot: target.getAttribute("data-slot") || "",
recurrence: target.getAttribute("data-recurrence") || "",
date: target.getAttribute("data-date") || "",
summary: target.getAttribute("data-summary") || "",
start: target.getAttribute("data-start") || "",
end: target.getAttribute("data-end") || "",
location: target.getAttribute("data-location") || "",
description: target.getAttribute("data-description") || "",
allDay: target.getAttribute("data-allday") === "1",
});
return;
}
// Event delete button
if (
target.classList &&
target.classList.contains("luxtools-event-delete-btn")
) {
e.preventDefault();
EventDelete.confirmDelete(target);
return;
}
// Confirm delete button
if (
target.classList &&
target.classList.contains("luxtools-event-confirm-delete")
) {
e.preventDefault();
EventDelete.executeDelete(target);
return;
}
// Confirm edit scope button (recurring event edit)
if (
target.classList &&
target.classList.contains("luxtools-event-confirm-edit-scope")
) {
e.preventDefault();
if (_pendingEditFormData) {
EventForm.submitSave(
target,
_pendingEditFormData,
target.getAttribute("data-scope") || "all",
);
_pendingEditFormData = null;
}
return;
}
// Event popup: clicking an event item within the day popup
// (items inside the popup that have data-luxtools-event)
var dayPopupEvent = target.closest
? target.closest(".luxtools-day-popup-event-item[data-luxtools-event]")
: null;
if (dayPopupEvent) {
if (
target.tagName === "BUTTON" ||
(target.closest && target.closest("button"))
)
return;
e.preventDefault();
EventPopup.open(dayPopupEvent, { hideDatetime: true });
return;
}
// Event popup: find closest element with data-luxtools-event
var eventEl = target.closest
? target.closest("[data-luxtools-event]")
: null;
if (!eventEl) {
var el = target;
while (el && el !== document) {
if (
el.getAttribute &&
el.getAttribute("data-luxtools-event") === "1"
) {
eventEl = el;
break;
}
el = el.parentNode;
}
}
if (eventEl) {
if (
target.tagName === "BUTTON" ||
(target.closest && target.closest("button"))
)
return;
// Check if this event is inside a day cell (calendar context)
var dayCell = eventEl.closest
? eventEl.closest('td[data-luxtools-day="1"]')
: null;
e.preventDefault();
EventPopup.open(eventEl, { hideDatetime: !!dayCell });
return;
}
// Day cell click: open day popup when clicking empty space
var clickedDayCell = target.closest
? target.closest('td[data-luxtools-day="1"]')
: null;
if (clickedDayCell) {
// Don't interfere with link clicks inside the cell
if (target.tagName === "A" || (target.closest && target.closest("a")))
return;
e.preventDefault();
DayPopup.open(clickedDayCell);
return;
}
},
false,
);
Luxtools.EventPopup = EventPopup;
Luxtools.DayPopup = DayPopup;
Luxtools.MaintenanceTasks = MaintenanceTasks;
})();