Unify dialog infrastructure
This commit is contained in:
95
js/dialog.js
Normal file
95
js/dialog.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/* global window, document */
|
||||
|
||||
/**
|
||||
* Unified Dialog Infrastructure
|
||||
*
|
||||
* Provides a shared modal overlay and dialog container that all other
|
||||
* client-side modules (event popups, cache purge, etc.) can use.
|
||||
*
|
||||
* Usage:
|
||||
* Luxtools.Dialog.show(htmlString) – render content and open
|
||||
* Luxtools.Dialog.close() – close the dialog
|
||||
* Luxtools.Dialog.getContainer() – return the dialog DOM element
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Luxtools = window.Luxtools || (window.Luxtools = {});
|
||||
|
||||
var Dialog = (function () {
|
||||
var overlay = null;
|
||||
var container = null;
|
||||
|
||||
/**
|
||||
* Lazily create the overlay + dialog container and attach them to
|
||||
* the document body. Wires up click-outside-to-close and Escape.
|
||||
*/
|
||||
function ensureElements() {
|
||||
if (overlay) return;
|
||||
|
||||
overlay = document.createElement('div');
|
||||
overlay.className = 'luxtools-dialog-overlay';
|
||||
overlay.style.display = 'none';
|
||||
|
||||
container = document.createElement('div');
|
||||
container.className = 'luxtools-dialog';
|
||||
container.setAttribute('role', 'dialog');
|
||||
container.setAttribute('aria-modal', 'true');
|
||||
|
||||
overlay.appendChild(container);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Close when clicking the backdrop (but not the dialog itself)
|
||||
overlay.addEventListener('click', function (e) {
|
||||
if (e.target === overlay) close();
|
||||
});
|
||||
|
||||
// Close on Escape
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && overlay && overlay.style.display !== 'none') {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dialog with the given HTML content.
|
||||
*
|
||||
* The HTML should include a close button with class
|
||||
* `luxtools-dialog-close` – it will be wired up automatically.
|
||||
*
|
||||
* @param {string} html - innerHTML for the dialog container
|
||||
*/
|
||||
function show(html) {
|
||||
ensureElements();
|
||||
container.innerHTML = html;
|
||||
overlay.style.display = 'flex';
|
||||
|
||||
// Auto-wire the close button inside the rendered content
|
||||
var closeBtn = container.querySelector('.luxtools-dialog-close');
|
||||
if (closeBtn) closeBtn.addEventListener('click', close);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close (hide) the dialog.
|
||||
*/
|
||||
function close() {
|
||||
if (overlay) overlay.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dialog container element (creates it if necessary).
|
||||
* Useful for querying form inputs after `show()`.
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
function getContainer() {
|
||||
ensureElements();
|
||||
return container;
|
||||
}
|
||||
|
||||
return { show: show, close: close, getContainer: getContainer };
|
||||
})();
|
||||
|
||||
Luxtools.Dialog = Dialog;
|
||||
})();
|
||||
1236
js/event-popup.js
1236
js/event-popup.js
File diff suppressed because it is too large
Load Diff
330
js/main.js
330
js/main.js
@@ -1,7 +1,7 @@
|
||||
/* global window, document */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
var Luxtools = window.Luxtools || (window.Luxtools = {});
|
||||
var Lightbox = Luxtools.Lightbox;
|
||||
@@ -16,7 +16,7 @@
|
||||
function findOpenElement(target) {
|
||||
var el = target;
|
||||
while (el && el !== document) {
|
||||
if (el.classList && el.classList.contains('luxtools-open')) return el;
|
||||
if (el.classList && el.classList.contains("luxtools-open")) return el;
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
@@ -25,7 +25,8 @@
|
||||
function findGalleryItem(target) {
|
||||
var el = target;
|
||||
while (el && el !== document) {
|
||||
if (el.classList && el.classList.contains('luxtools-gallery-item')) return el;
|
||||
if (el.classList && el.classList.contains("luxtools-gallery-item"))
|
||||
return el;
|
||||
el = el.parentNode;
|
||||
}
|
||||
return null;
|
||||
@@ -35,7 +36,9 @@
|
||||
// Image gallery lightbox: intercept clicks so we don't navigate away.
|
||||
var galleryItem = findGalleryItem(event.target);
|
||||
if (galleryItem && Lightbox && Lightbox.open) {
|
||||
var gallery = galleryItem.closest ? galleryItem.closest('div.luxtools-gallery[data-luxtools-gallery="1"]') : null;
|
||||
var gallery = galleryItem.closest
|
||||
? galleryItem.closest('div.luxtools-gallery[data-luxtools-gallery="1"]')
|
||||
: null;
|
||||
if (gallery) {
|
||||
event.preventDefault();
|
||||
Lightbox.open(gallery, galleryItem);
|
||||
@@ -47,49 +50,56 @@
|
||||
if (!el) return;
|
||||
|
||||
// {{open>...}} renders as a link; avoid jumping to '#'.
|
||||
if (el.tagName && el.tagName.toLowerCase() === 'a') {
|
||||
if (el.tagName && el.tagName.toLowerCase() === "a") {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
var raw = el.getAttribute('data-path') || '';
|
||||
var raw = el.getAttribute("data-path") || "";
|
||||
if (!raw) return;
|
||||
if (!OpenService || !OpenService.openViaService) return;
|
||||
|
||||
// Prefer local client service.
|
||||
OpenService.openViaService(el, raw)
|
||||
.catch(function (err) {
|
||||
// If the browser blocks the request before it reaches localhost (mixed-content,
|
||||
// extensions, stricter CORS handling), fall back to a no-CORS GET ping.
|
||||
if (OpenService && OpenService.pingOpenViaImage) {
|
||||
OpenService.pingOpenViaImage(el, raw);
|
||||
}
|
||||
OpenService.openViaService(el, raw).catch(function (err) {
|
||||
// If the browser blocks the request before it reaches localhost (mixed-content,
|
||||
// extensions, stricter CORS handling), fall back to a no-CORS GET ping.
|
||||
if (OpenService && OpenService.pingOpenViaImage) {
|
||||
OpenService.pingOpenViaImage(el, raw);
|
||||
}
|
||||
|
||||
// Fallback to old behavior (often blocked in modern browsers).
|
||||
var url = OpenService && OpenService.normalizeToFileUrl ? OpenService.normalizeToFileUrl(raw) : '';
|
||||
if (!url) return;
|
||||
console.warn('Local client service failed, falling back to file:// navigation:', err);
|
||||
// Fallback to old behavior (often blocked in modern browsers).
|
||||
var url =
|
||||
OpenService && OpenService.normalizeToFileUrl
|
||||
? OpenService.normalizeToFileUrl(raw)
|
||||
: "";
|
||||
if (!url) return;
|
||||
console.warn(
|
||||
"Local client service failed, falling back to file:// navigation:",
|
||||
err,
|
||||
);
|
||||
try {
|
||||
window.open(url, "_blank", "noopener");
|
||||
} catch (e) {
|
||||
try {
|
||||
window.open(url, '_blank', 'noopener');
|
||||
} catch (e) {
|
||||
try {
|
||||
window.location.href = url;
|
||||
} catch (e2) {
|
||||
console.error('Failed to open file URL:', e2);
|
||||
}
|
||||
window.location.href = url;
|
||||
} catch (e2) {
|
||||
console.error("Failed to open file URL:", e2);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initChronologicalEventTimes() {
|
||||
var nodes = document.querySelectorAll('.luxtools-event-time[data-luxtools-start]');
|
||||
var nodes = document.querySelectorAll(
|
||||
".luxtools-event-time[data-luxtools-start]",
|
||||
);
|
||||
if (!nodes || nodes.length === 0) return;
|
||||
|
||||
var formatter;
|
||||
try {
|
||||
formatter = new Intl.DateTimeFormat('de-DE', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
formatter = new Intl.DateTimeFormat("de-DE", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
});
|
||||
} catch (e) {
|
||||
formatter = null;
|
||||
@@ -97,7 +107,7 @@
|
||||
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
var raw = node.getAttribute('data-luxtools-start') || '';
|
||||
var raw = node.getAttribute("data-luxtools-start") || "";
|
||||
if (!raw) continue;
|
||||
|
||||
var date = new Date(raw);
|
||||
@@ -107,9 +117,9 @@
|
||||
if (formatter) {
|
||||
label = formatter.format(date);
|
||||
} else {
|
||||
var hh = String(date.getHours()).padStart(2, '0');
|
||||
var mm = String(date.getMinutes()).padStart(2, '0');
|
||||
label = hh + ':' + mm;
|
||||
var hh = String(date.getHours()).padStart(2, "0");
|
||||
var mm = String(date.getMinutes()).padStart(2, "0");
|
||||
label = hh + ":" + mm;
|
||||
}
|
||||
|
||||
node.textContent = label;
|
||||
@@ -120,123 +130,185 @@
|
||||
// Purge Cache Dialog
|
||||
// ============================================================
|
||||
function initPurgeCacheDialog() {
|
||||
var $link = jQuery('a.luxtools-invalidate-cache');
|
||||
if ($link.length === 0) return;
|
||||
document.addEventListener(
|
||||
"click",
|
||||
function (e) {
|
||||
var link = e.target.closest
|
||||
? e.target.closest("a.luxtools-invalidate-cache")
|
||||
: null;
|
||||
if (!link) return;
|
||||
|
||||
$link.on('click.luxtools', function (e) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
var href = $link.attr('href') || '';
|
||||
var lang = (window.LANG && window.LANG.plugins && window.LANG.plugins.luxtools)
|
||||
? window.LANG.plugins.luxtools
|
||||
: {};
|
||||
var href = link.getAttribute("href") || "";
|
||||
var lang =
|
||||
window.LANG && window.LANG.plugins && window.LANG.plugins.luxtools
|
||||
? window.LANG.plugins.luxtools
|
||||
: {};
|
||||
|
||||
var $dialog = jQuery(
|
||||
'<div>' +
|
||||
'<p>' + (lang.cache_purge_dialog_intro || 'The DokuWiki cache will always be purged. Optionally also purge the luxtools-specific caches:') + '</p>' +
|
||||
'<p><label><input type="checkbox" id="luxtools-purge-pagelinks"> <strong>' + (lang.cache_purge_pagelinks_label || 'Pagelinks') + '</strong>' +
|
||||
' – ' + (lang.cache_purge_pagelinks_desc || 'Purges the pagelink mapping cache') + '</label></p>' +
|
||||
'<p><label><input type="checkbox" id="luxtools-purge-thumbs"> <strong>' + (lang.cache_purge_thumbs_label || 'Thumbnails') + '</strong>' +
|
||||
' – ' + (lang.cache_purge_thumbs_desc || 'Purges all cached image thumbnails') + '</label></p>' +
|
||||
'</div>'
|
||||
);
|
||||
var html = '<div class="luxtools-dialog-content">';
|
||||
html +=
|
||||
'<button type="button" class="luxtools-dialog-close" aria-label="Close">×</button>';
|
||||
html +=
|
||||
'<h3 class="luxtools-dialog-title">' +
|
||||
(lang.cache_purge_dialog_title || "Purge Cache") +
|
||||
"</h3>";
|
||||
html +=
|
||||
"<p>" +
|
||||
(lang.cache_purge_dialog_intro ||
|
||||
"The DokuWiki cache will always be purged. Optionally also purge the luxtools-specific caches:") +
|
||||
"</p>";
|
||||
html +=
|
||||
'<p><label><input type="checkbox" id="luxtools-purge-pagelinks"> <strong>' +
|
||||
(lang.cache_purge_pagelinks_label || "Pagelinks") +
|
||||
"</strong>";
|
||||
html +=
|
||||
" – " +
|
||||
(lang.cache_purge_pagelinks_desc ||
|
||||
"Purges the pagelink mapping cache") +
|
||||
"</label></p>";
|
||||
html +=
|
||||
'<p><label><input type="checkbox" id="luxtools-purge-thumbs"> <strong>' +
|
||||
(lang.cache_purge_thumbs_label || "Thumbnails") +
|
||||
"</strong>";
|
||||
html +=
|
||||
" – " +
|
||||
(lang.cache_purge_thumbs_desc ||
|
||||
"Purges all cached image thumbnails") +
|
||||
"</label></p>";
|
||||
html += '<div class="luxtools-dialog-actions">';
|
||||
html +=
|
||||
'<button type="button" class="button luxtools-purge-confirm">' +
|
||||
(lang.cache_purge_confirm || "Purge Cache") +
|
||||
"</button> ";
|
||||
html +=
|
||||
'<button type="button" class="button luxtools-purge-cancel">' +
|
||||
(lang.cache_purge_cancel || "Cancel") +
|
||||
"</button>";
|
||||
html += "</div>";
|
||||
html += "</div>";
|
||||
|
||||
$dialog.dialog({
|
||||
title: lang.cache_purge_dialog_title || 'Purge Cache',
|
||||
modal: true,
|
||||
width: 420,
|
||||
buttons: [
|
||||
{
|
||||
text: lang.cache_purge_cancel || 'Cancel',
|
||||
click: function () { $dialog.dialog('close'); }
|
||||
},
|
||||
{
|
||||
text: lang.cache_purge_confirm || 'Purge Cache',
|
||||
click: function () {
|
||||
var url = href;
|
||||
if ($dialog.find('#luxtools-purge-pagelinks').prop('checked')) {
|
||||
url += '&luxtools_purge_pagelinks=1';
|
||||
}
|
||||
if ($dialog.find('#luxtools-purge-thumbs').prop('checked')) {
|
||||
url += '&luxtools_purge_thumbs=1';
|
||||
}
|
||||
$dialog.dialog('close');
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
],
|
||||
close: function () {
|
||||
jQuery(this).dialog('destroy').remove();
|
||||
Luxtools.Dialog.show(html);
|
||||
|
||||
var container = Luxtools.Dialog.getContainer();
|
||||
|
||||
var cancelBtn = container.querySelector(".luxtools-purge-cancel");
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener("click", function () {
|
||||
Luxtools.Dialog.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var confirmBtn = container.querySelector(".luxtools-purge-confirm");
|
||||
if (confirmBtn) {
|
||||
confirmBtn.addEventListener("click", function () {
|
||||
var url = href;
|
||||
var plCheck = container.querySelector("#luxtools-purge-pagelinks");
|
||||
var thCheck = container.querySelector("#luxtools-purge-thumbs");
|
||||
if (plCheck && plCheck.checked) {
|
||||
url += "&luxtools_purge_pagelinks=1";
|
||||
}
|
||||
if (thCheck && thCheck.checked) {
|
||||
url += "&luxtools_purge_thumbs=1";
|
||||
}
|
||||
Luxtools.Dialog.close();
|
||||
window.location.href = url;
|
||||
});
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Calendar Sync Button (syntax widget)
|
||||
// ============================================================
|
||||
function initCalendarSyncButtons() {
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target;
|
||||
if (!btn || !btn.classList || !btn.classList.contains('luxtools-calendar-sync-btn')) return;
|
||||
document.addEventListener(
|
||||
"click",
|
||||
function (e) {
|
||||
var btn = e.target;
|
||||
if (
|
||||
!btn ||
|
||||
!btn.classList ||
|
||||
!btn.classList.contains("luxtools-calendar-sync-btn")
|
||||
)
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
var ajaxUrl = btn.getAttribute('data-luxtools-ajax-url') || '';
|
||||
var sectok = btn.getAttribute('data-luxtools-sectok') || '';
|
||||
if (!ajaxUrl) return;
|
||||
var ajaxUrl = btn.getAttribute("data-luxtools-ajax-url") || "";
|
||||
var sectok = btn.getAttribute("data-luxtools-sectok") || "";
|
||||
if (!ajaxUrl) return;
|
||||
|
||||
var status = btn.parentNode ? btn.parentNode.querySelector('.luxtools-calendar-sync-status') : null;
|
||||
var status = btn.parentNode
|
||||
? btn.parentNode.querySelector(".luxtools-calendar-sync-status")
|
||||
: null;
|
||||
|
||||
btn.disabled = true;
|
||||
if (status) {
|
||||
status.textContent = 'Syncing...';
|
||||
status.style.color = '';
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', ajaxUrl, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.onload = function () {
|
||||
btn.disabled = false;
|
||||
try {
|
||||
var r = JSON.parse(xhr.responseText);
|
||||
if (status) {
|
||||
status.textContent = r.message || (r.ok ? 'Done' : 'Failed');
|
||||
status.style.color = r.ok ? 'green' : 'red';
|
||||
}
|
||||
} catch (ex) {
|
||||
if (status) {
|
||||
status.textContent = 'Error';
|
||||
status.style.color = 'red';
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
btn.disabled = false;
|
||||
btn.disabled = true;
|
||||
if (status) {
|
||||
status.textContent = 'Network error';
|
||||
status.style.color = 'red';
|
||||
status.textContent = "Syncing...";
|
||||
status.style.color = "";
|
||||
}
|
||||
};
|
||||
xhr.send('call=luxtools_calendar_sync§ok=' + encodeURIComponent(sectok));
|
||||
}, false);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", ajaxUrl, true);
|
||||
xhr.setRequestHeader(
|
||||
"Content-Type",
|
||||
"application/x-www-form-urlencoded",
|
||||
);
|
||||
xhr.onload = function () {
|
||||
btn.disabled = false;
|
||||
try {
|
||||
var r = JSON.parse(xhr.responseText);
|
||||
if (status) {
|
||||
status.textContent = r.message || (r.ok ? "Done" : "Failed");
|
||||
status.style.color = r.ok ? "green" : "red";
|
||||
}
|
||||
} catch (ex) {
|
||||
if (status) {
|
||||
status.textContent = "Error";
|
||||
status.style.color = "red";
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
btn.disabled = false;
|
||||
if (status) {
|
||||
status.textContent = "Network error";
|
||||
status.style.color = "red";
|
||||
}
|
||||
};
|
||||
xhr.send(
|
||||
"call=luxtools_calendar_sync§ok=" + encodeURIComponent(sectok),
|
||||
);
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Initialize
|
||||
// ============================================================
|
||||
document.addEventListener('click', onClick, false);
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (GalleryThumbnails && GalleryThumbnails.init) GalleryThumbnails.init();
|
||||
initChronologicalEventTimes();
|
||||
if (CalendarWidget && CalendarWidget.init) CalendarWidget.init();
|
||||
initPurgeCacheDialog();
|
||||
initCalendarSyncButtons();
|
||||
}, false);
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (Scratchpads && Scratchpads.init) Scratchpads.init();
|
||||
}, false);
|
||||
document.addEventListener("click", onClick, false);
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
function () {
|
||||
if (GalleryThumbnails && GalleryThumbnails.init) GalleryThumbnails.init();
|
||||
initChronologicalEventTimes();
|
||||
if (CalendarWidget && CalendarWidget.init) CalendarWidget.init();
|
||||
initPurgeCacheDialog();
|
||||
initCalendarSyncButtons();
|
||||
},
|
||||
false,
|
||||
);
|
||||
document.addEventListener(
|
||||
"DOMContentLoaded",
|
||||
function () {
|
||||
if (Scratchpads && Scratchpads.init) Scratchpads.init();
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
Luxtools.initChronologicalEventTimes = initChronologicalEventTimes;
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user