Files
luxtools-plugin/js/event-popup.js
2026-03-11 13:50:45 +01:00

324 lines
10 KiB
JavaScript

/* global window, document, jQuery */
/**
* Event Popup and Maintenance Task Handling
*
* - Clicking an event item with data-luxtools-event="1" opens a detail popup.
* - Clicking a maintenance task action button sends an AJAX request to
* complete/reopen the task.
*/
(function () {
'use strict';
var Luxtools = window.Luxtools || (window.Luxtools = {});
// ============================================================
// Event Popup
// ============================================================
var EventPopup = (function () {
var overlay = null;
var popup = null;
function ensureElements() {
if (overlay) return;
overlay = document.createElement('div');
overlay.className = 'luxtools-event-popup-overlay';
overlay.style.display = 'none';
popup = document.createElement('div');
popup.className = 'luxtools-event-popup';
popup.setAttribute('role', 'dialog');
popup.setAttribute('aria-modal', 'true');
overlay.appendChild(popup);
document.body.appendChild(overlay);
overlay.addEventListener('click', function (e) {
if (e.target === overlay) {
close();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && overlay.style.display !== 'none') {
close();
}
});
}
function open(el) {
ensureElements();
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 html = '<div class="luxtools-event-popup-content">';
html += '<button type="button" class="luxtools-event-popup-close" aria-label="Close">&times;</button>';
html += '<h3 class="luxtools-event-popup-title">' + escapeHtml(summary) + '</h3>';
// Date/time
html += '<div class="luxtools-event-popup-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>';
if (location) {
html += '<div class="luxtools-event-popup-field"><strong>Location:</strong> ' + escapeHtml(location) + '</div>';
}
if (description) {
html += '<div class="luxtools-event-popup-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>';
}
html += '</div>';
popup.innerHTML = html;
overlay.style.display = 'flex';
// Close button inside popup
var closeBtn = popup.querySelector('.luxtools-event-popup-close');
if (closeBtn) {
closeBtn.addEventListener('click', close);
}
}
function close() {
if (overlay) {
overlay.style.display = 'none';
}
}
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 isSameMoment(left, right) {
if (!left || !right) return false;
return left === right;
}
function pad2(value) {
return String(value).padStart(2, '0');
}
function escapeHtml(text) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}
return {
open: open,
close: close,
};
})();
// ============================================================
// Maintenance Task Actions
// ============================================================
var MaintenanceTasks = (function () {
function getSecurityToken(container) {
var sectok = container ? container.getAttribute('data-luxtools-sectok') : '';
if (sectok) return String(sectok);
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 handleAction(button) {
var action = button.getAttribute('data-action');
if (!action) return;
// Find the containing list item or container with task data
var item = button.closest('[data-task-uid]');
if (!item) {
// Try the syntax plugin format
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;
// Find AJAX URL and security token from parent container or global
var container = item.closest('[data-luxtools-ajax-url]');
var ajaxUrl = container ? container.getAttribute('data-luxtools-ajax-url') : '';
var sectok = getSecurityToken(container);
if (!ajaxUrl) {
// Fallback: use DokuWiki's standard AJAX endpoint
ajaxUrl = (window.DOKU_BASE || '/') + 'lib/exe/ajax.php';
}
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) {
// Visual feedback: mark item as done or revert
if (action === 'complete') {
item.classList.add('luxtools-task-completed');
button.textContent = 'Reopen';
button.setAttribute('data-action', 'reopen');
button.disabled = false;
} else {
item.classList.remove('luxtools-task-completed');
item.style.opacity = '1';
button.textContent = 'Complete';
button.setAttribute('data-action', 'complete');
button.disabled = false;
}
// Show remote write warning if applicable
if (result.remoteOk === false && result.remoteError) {
showNotification(result.remoteError, 'warning');
}
} else {
var errMsg = result.error || 'Action failed';
showNotification(errMsg, '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);
}
function showNotification(message, type) {
// Use DokuWiki msg() if available
if (typeof window.msg === 'function') {
var level = (type === 'error') ? -1 : ((type === 'warning') ? 0 : 1);
window.msg(message, level);
return;
}
// Fallback: simple notification
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);
}
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 popup: find closest element with data-luxtools-event
var eventEl = target.closest ? target.closest('[data-luxtools-event]') : null;
if (!eventEl) {
// Traverse manually for older browsers
var el = target;
while (el && el !== document) {
if (el.getAttribute && el.getAttribute('data-luxtools-event') === '1') {
eventEl = el;
break;
}
el = el.parentNode;
}
}
if (eventEl) {
// Don't open popup if clicking a button inside the event item
if (target.tagName === 'BUTTON' || target.closest('button')) return;
e.preventDefault();
EventPopup.open(eventEl);
}
}, false);
Luxtools.EventPopup = EventPopup;
Luxtools.MaintenanceTasks = MaintenanceTasks;
})();