Calendar Sync V1
This commit is contained in:
303
js/event-popup.js
Normal file
303
js/event-popup.js
Normal file
@@ -0,0 +1,303 @@
|
||||
/* 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) {
|
||||
html += ' – ' + formatDate(end);
|
||||
}
|
||||
} else {
|
||||
html += '<strong>Time:</strong> ' + formatDateTime(start);
|
||||
if (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 d.toLocaleDateString();
|
||||
}
|
||||
|
||||
function formatDateTime(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
var d = new Date(isoStr);
|
||||
if (isNaN(d.getTime())) return isoStr;
|
||||
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
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 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 = container ? container.getAttribute('data-luxtools-sectok') : '';
|
||||
|
||||
if (!ajaxUrl) {
|
||||
// Fallback: use DokuWiki's standard AJAX endpoint
|
||||
ajaxUrl = (window.DOKU_BASE || '/') + 'lib/exe/ajax.php';
|
||||
}
|
||||
if (!sectok && window.JSINFO && window.JSINFO.sectok) {
|
||||
sectok = window.JSINFO.sectok;
|
||||
}
|
||||
|
||||
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');
|
||||
// Fade out and remove after a short delay
|
||||
item.style.opacity = '0.5';
|
||||
setTimeout(function () {
|
||||
item.style.display = 'none';
|
||||
}, 1000);
|
||||
} 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;
|
||||
})();
|
||||
Reference in New Issue
Block a user