170 lines
6.6 KiB
JavaScript
170 lines
6.6 KiB
JavaScript
leantime.modals = (function () {
|
|
|
|
// The URL of the modal currently open. Used to make the hashchange->openModal
|
|
// path idempotent: a repeated hashchange pointing at the already-open modal
|
|
// must NOT rebuild it. Rebuilding re-fetches and re-inserts the whole
|
|
// .nyroModalCont, destroying any field the user is typing into (focus loss).
|
|
var currentModalUrl = null;
|
|
|
|
var setCustomModalCallback = function(callback) {
|
|
if(typeof callback === 'function') {
|
|
window.globalModalCallback = callback;
|
|
}
|
|
}
|
|
var openModal = function () {
|
|
|
|
var modalOptions = {
|
|
sizes: {
|
|
minW: 500,
|
|
minH: 200
|
|
},
|
|
resizable: true,
|
|
autoSizable: true,
|
|
callbacks: {
|
|
beforePostSubmit: function () {
|
|
|
|
jQuery(".showDialogOnLoad").show();
|
|
|
|
// Destroy Tiptap editors
|
|
if(window.leantime?.tiptapController?.registry) {
|
|
var count = window.leantime.tiptapController.registry.destroyAll();
|
|
if(count > 0) {
|
|
console.log('[Modal] Destroyed', count, 'Tiptap editor(s)');
|
|
}
|
|
}
|
|
|
|
},
|
|
beforeShowCont: function () {
|
|
jQuery(".showDialogOnLoad").show();
|
|
|
|
// Destroy Tiptap editors
|
|
if(window.leantime?.tiptapController?.registry) {
|
|
window.leantime.tiptapController.registry.destroyAll();
|
|
}
|
|
|
|
},
|
|
afterShowCont: function () {
|
|
window.htmx.process('.nyroModalCont');
|
|
jQuery(".formModal, .modal").nyroModal(modalOptions);
|
|
// Idempotent + scoped to the modal so it doesn't re-instance
|
|
// page tooltips (see app.js initTooltips).
|
|
window.leantime?.initTooltips?.(document.querySelector('.nyroModalCont'));
|
|
|
|
// Initialize Tiptap editors in modal (after small delay for DOM settlement)
|
|
setTimeout(function() {
|
|
if(window.leantime?.tiptapController?.initEditors) {
|
|
var modalContent = document.querySelector('.nyroModalCont');
|
|
if(modalContent) {
|
|
window.leantime.tiptapController.initEditors(modalContent);
|
|
}
|
|
}
|
|
}, 100);
|
|
},
|
|
beforeClose: function () {
|
|
currentModalUrl = null;
|
|
try{
|
|
history.pushState("", document.title, window.location.pathname + window.location.search);
|
|
|
|
}catch(error){
|
|
//Code to handle error comes here
|
|
console.log("Issue pushing history");
|
|
}
|
|
|
|
if(typeof window.globalModalCallback === 'function') {
|
|
window.globalModalCallback();
|
|
}else{
|
|
location.reload();
|
|
}
|
|
}
|
|
},
|
|
titleFromIframe: true
|
|
};
|
|
|
|
var url = window.location.hash.substring(1);
|
|
if(url.includes("showTicket")
|
|
|| url.includes("ideaDialog")
|
|
|| url.includes("articleDialog")) {
|
|
// These detail modals are intentionally large on desktop. On
|
|
// mobile/tablet (<1200px) the 1800px minimum makes them unusable,
|
|
// so only apply it on desktop. CSS caps the container to ~95vw. #3088
|
|
if (window.innerWidth >= 1200) {
|
|
modalOptions.sizes.minW = 1800;
|
|
modalOptions.sizes.minH = 1800;
|
|
}
|
|
}
|
|
|
|
// Never let any modal's minimum width exceed the viewport on small
|
|
// screens, otherwise it forces horizontal overflow. #3088
|
|
if (window.innerWidth < 1200) {
|
|
modalOptions.sizes.minW = Math.min(modalOptions.sizes.minW, window.innerWidth - 20);
|
|
}
|
|
|
|
//Ensure we have no trailing slash at the end.
|
|
var baseUrl = leantime.appUrl.replace(/\/$/, '');
|
|
|
|
var urlParts = url.split("/");
|
|
if(urlParts.length>2 && urlParts[1] !== "tab") {
|
|
var targetUrl = baseUrl+""+url;
|
|
|
|
// Idempotency guard: if the modal for this exact URL is already open, a
|
|
// repeated hashchange must NOT rebuild it — rebuilding destroys the DOM
|
|
// (and any input the user is typing into), stealing focus.
|
|
if (targetUrl === currentModalUrl && jQuery.nmTop()) {
|
|
return;
|
|
}
|
|
currentModalUrl = targetUrl;
|
|
|
|
// Guard against nyroModal losing its jQuery registration between opens.
|
|
// This can happen when the modal close/reinit cycle runs before the
|
|
// document-ready wrapper in jquery.nyroModal.custom.js has re-fired.
|
|
if (typeof jQuery.nmManual !== 'function') {
|
|
console.warn('[Modal] jQuery.nmManual not available, retrying...');
|
|
setTimeout(function() {
|
|
if (typeof jQuery.nmManual === 'function') {
|
|
jQuery.nmManual(targetUrl, modalOptions);
|
|
} else {
|
|
console.error('[Modal] jQuery.nmManual unavailable after retry — nyroModal may not be loaded.');
|
|
}
|
|
}, 100);
|
|
return;
|
|
}
|
|
jQuery.nmManual(targetUrl, modalOptions);
|
|
}
|
|
}
|
|
|
|
var closeModal = function () {
|
|
if( jQuery.nmTop()) {
|
|
jQuery.nmTop().close();
|
|
}
|
|
}
|
|
|
|
return {
|
|
openModal:openModal,
|
|
setCustomModalCallback:setCustomModalCallback,
|
|
closeModal:closeModal
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
jQuery(document).ready(function() {
|
|
leantime.modals.openModal();
|
|
});
|
|
|
|
window.addEventListener("hashchange", function () {
|
|
leantime.modals.openModal();
|
|
});
|
|
|
|
// 'lt:ui:modal.close' is the canonical client event. The legacy names ('closeModal',
|
|
// 'HTMX.closemodal', 'Htmx.CloseModal') are kept for the migration window and also close a
|
|
// pre-existing gap: emitters used three different casings but only 'closeModal' had a listener.
|
|
var onCloseModalEvent = function (evt) {
|
|
leantime.modals.closeModal();
|
|
};
|
|
|
|
window.addEventListener("lt:ui:modal.close", onCloseModalEvent);
|
|
window.addEventListener("closeModal", onCloseModalEvent);
|
|
window.addEventListener("HTMX.closemodal", onCloseModalEvent);
|
|
window.addEventListener("Htmx.CloseModal", onCloseModalEvent);
|
|
|