324 lines
10 KiB
JavaScript
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">×</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 += ' – ' + formatDate(end);
|
|
}
|
|
} else {
|
|
html += '<strong>Time:</strong> ' + formatDateTime(start);
|
|
if (end && !isSameMoment(start, end)) {
|
|
html += ' – ' + 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)
|
|
+ '§ok=' + 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;
|
|
})();
|