'use strict' // State-changing admin actions are sent as POST requests carrying the CSRF token // exposed in the tag of the layout. function postTo(url, params = {}) { const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); const form = document.createElement('form'); form.method = 'post'; form.action = url; form.classList.add('d-none'); const fields = Object.assign({ _token: token }, params); Object.keys(fields).forEach(name => { const input = document.createElement('input'); input.type = 'hidden'; input.name = name; input.value = fields[name]; form.appendChild(input); }); document.body.appendChild(form); form.submit(); } // Any element with a data-post-href attribute triggers a POST to that URL when clicked document.addEventListener('click', event => { const trigger = event.target.closest('[data-post-href]'); if (!trigger) { return; } event.preventDefault(); postTo(trigger.getAttribute('data-post-href')); }); // Clicking an element with data-clear-target empties the input with that id. // Native date inputs have no good way to clear the value. document.addEventListener('click', event => { const trigger = event.target.closest('[data-clear-target]'); if (!trigger) { return; } const input = document.getElementById(trigger.getAttribute('data-clear-target')); if (input) { input.value = ''; input.dispatchEvent(new Event('change', { bubbles: true })); } }); // Calendar share modal const shareModal = document.getElementById('shareModal') if (shareModal) { const addShareeButton = document.getElementById('shareModal-addSharee'); // Bound once: the target url is refreshed each time the modal is shown addShareeButton.addEventListener("click", function(e) { const writeAccess = document.getElementById('shareModal-writeAccess').checked ? 'true' : 'false'; const principalId = document.getElementById('shareModal-member').value; e.preventDefault() postTo(addShareeButton.getAttribute('data-href'), { principalId: principalId, write: writeAccess }) }); shareModal.addEventListener('show.bs.modal', event => { // Button that triggered the modal const button = event.relatedTarget // Grab calendar shares url and add url let shareesUrl = button.getAttribute('data-sharees-href'); addShareeButton.setAttribute('data-href', button.getAttribute('data-href')); const noneElement = document.getElementById('shareModal-none') // Shares list const shares = document.getElementById('shareModal-shares') shares.innerHTML = '' // Get calendar shares fetch(shareesUrl) .then((response) => response.json()) .then((data) => { // No sharee if (data.length === 0) { noneElement.classList.remove("d-none"); return } noneElement.classList.add('d-none') // Share list item template const template = document.getElementById("shareModal-shareeTemplate"); data.forEach(element => { const clone = template.content.cloneNode(true); let name = clone.querySelectorAll("span.name"); name[0].textContent = element.displayName; let badge = clone.querySelectorAll("span.badge"); badge[0].textContent = element.accessText; if (element.isWriteAccess) { badge[0].classList.add('bg-success') badge[0].classList.remove('bg-info') } let revokeButton = clone.querySelectorAll(".revoke"); revokeButton[0].setAttribute('data-post-href', element.revokeUrl); shares.appendChild(clone); }); }); }) } // Delete modals (all kind of entities, so we use the rel, not the id) document.querySelectorAll('[rel="deleteModal"]').forEach(element => { element.addEventListener('show.bs.modal', event => { // Button that triggered the modal const button = event.relatedTarget // Grab real target url for deletion let targetUrl = button.getAttribute('data-href'); let modalFlavour = button.getAttribute('data-flavour'); // Put it into the modal's OK button (which POSTs it on click) const deleteCTA = document.getElementById(`deleteModal-${modalFlavour}-cta`); deleteCTA.setAttribute('data-post-href', targetUrl); }) }) // Global account delegation modal const addDelegateModal = document.getElementById('addDelegateModal') if (addDelegateModal) { const addDelegateButton = document.getElementById('addDelegateModal-cta'); addDelegateButton.addEventListener("click", function(e) { const targetUrl = addDelegateButton.getAttribute('data-href'); const writeAccess = document.getElementById('addDelegateModal-writeAccess').checked ? 'true' : 'false'; const principalId = document.getElementById('addDelegateModal-member').value; e.preventDefault() postTo(targetUrl, { principalId: principalId, write: writeAccess }) }); } // Color swatch: update it live (not working in IE ¯\_(ツ)_/¯ but it's just a nice to have) const colorPicker = document.getElementById('calendar_instance_calendarColor'); if (colorPicker) { colorPicker.addEventListener('keyup', event => { document.body.style.setProperty('--calendar-color', event.target.value); }) document.body.style.setProperty('--calendar-color', colorPicker.value); } // Bootstrap 5 popovers // // The setup popovers show a calendar or address book uri, which its owner chooses over DAV. The // content is built here as DOM rather than handed to Bootstrap as an HTML string, so those values // go through textContent and are never parsed as markup. const buildPopoverContent = element => { const content = document.createDocumentFragment(); [['URI', element.dataset.uri], ['Absolute path', element.dataset.davUri]].forEach(([label, value], index) => { if (!value) { return; } if (index > 0) { content.appendChild(document.createElement('br')); } content.appendChild(document.createTextNode(`${label}: `)); const code = document.createElement('code'); code.textContent = value; content.appendChild(code); }) return content; } const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]') if (popoverTriggerList) { [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover( popoverTriggerEl, // Popovers that carry no uri keep their own static `data-bs-content` popoverTriggerEl.dataset.uri ? { html: true, content: buildPopoverContent(popoverTriggerEl) } : {} )) } // Bootstrap 5 toasts const toastElList = document.querySelectorAll('.toast') if (toastElList) { [...toastElList].map(toastEl => { const toast = new bootstrap.Toast(toastEl) toast.show() }) }