/** * SPDX-FileCopyrightText: 2023 jordi fita mas * SPDX-License-Identifier: AGPL-3.0-only */ function ready(fn) { if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } } ready(function () { const snackBar = Object.assign(document.body.appendChild(document.createElement('section')), { id: 'snackbar', }); const errorMessage = snackBar.appendChild(document.createElement('div')); errorMessage.setAttribute('role', 'alert'); const openClass = 'open'; const toasts = []; let timeoutId = null; function showError(message) { toasts.push(message); popUp(); } function popUp() { if (toasts.length === 0) { return; } if (errorMessage.classList.contains(openClass)) { dismiss(); return; } if (errorMessage.innerText !== "") { // it will show after remove calls popUp again. return; } errorMessage.innerText = toasts[0]; errorMessage.classList.add(openClass); timeoutId = setTimeout(dismiss, 4000); } function dismiss() { if (!errorMessage.classList.contains(openClass)) { // already dismissed return; } errorMessage.classList.remove(openClass); clearTimeout(timeoutId); timeoutId = setTimeout(remove, 350); } function remove() { clearTimeout(timeoutId); toasts.splice(0, 1); errorMessage.innerText = ""; popUp(); } document.body.addEventListener('htmx:error', function (evt) { const errorInfo = evt.detail.errorInfo; const error = errorInfo.xhr && errorInfo.xhr.responseText || errorInfo.error; showError(error); }); }) ready(function () { const textareas = document.querySelectorAll('textarea.html'); if (textareas.length > 0) { const language = document.documentElement.getAttribute('lang'); const csrfHeader = JSON.parse(document.querySelector('meta[name="csrf-header"]').getAttribute('content')); const script = document.head.appendChild(Object.assign(document.createElement('script'), { src: '/static/ckeditor5@40.2.0/ckeditor.js', })); if (language !== 'en') { document.head.appendChild(Object.assign(document.createElement('script'), { src: '/static/ckeditor5@40.2.0/translations/' + language + '.js', })); } const editorConfig = { language, image: { toolbar: [ 'imageTextAlternative', 'toggleImageCaption', '|', 'imageStyle:inline', 'imageStyle:wrapText', 'imageStyle:breakText', '|', 'resizeImage', ], }, simpleUpload: { uploadUrl: '/admin/media', headers: Object.assign(csrfHeader, { Accept: 'application/vnd.ckeditor+json', }), }, }; script.addEventListener('load', function () { for (const textarea of textareas) { const canvas = document.createElement('div'); textarea.parentNode.insertBefore(canvas, textarea.nextSibling); textarea.style.display = 'none'; ClassicEditor .create(canvas, editorConfig) .then(editor => { const xml = document.createElement('div'); const serializer = new XMLSerializer(); editor.setData(textarea.value); editor.ui.focusTracker.on('change:isFocused', (event, name, focused) => { if (!focused) { xml.innerHTML = editor.getData(); textarea.value = serializer.serializeToString(xml).replace(' ', ' '); } }); }) .catch(error => { console.error(error); }); } }); } }) export function camperUploadForm(el) { const progress = el.querySelector('progress'); htmx.on(el, 'drop', function (evt) { evt.preventDefault(); [...evt.dataTransfer.items].forEach(function (i) { console.log(i); i.getAsString(console.log) }); }); htmx.on(el, 'dragover', function (evt) { evt.preventDefault(); }); htmx.on(el, 'dragleave', function (evt) { evt.preventDefault(); }); htmx.on(el, 'htmx:xhr:progress', function (evt) { if (progress && evt.detail.lengthComputable) { progress.setAttribute('value', evt.detail.loaded / evt.detail.total * 100); } }); } export function setupCampgroundMap(map) { if (!map) { return; } const prefix = "cp_"; for (const campsite of Array.from(map.querySelectorAll(`[id^="${prefix}"]`))) { const label = campsite.id.substring(prefix.length); if (!label) { continue; } const link = document.createElementNS('http://www.w3.org/2000/svg', 'a'); link.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/admin/campsites/' + label); link.append(...campsite.childNodes); campsite.appendChild(link); } } export function setupIconInput(icon) { if (!icon) { return; } const input = icon.querySelector('input[type="hidden"]') if (!input) { return; } const buttons = Array.from(icon.querySelectorAll('button[data-icon-name]')); const updateValue = function (iconName) { input.value = iconName; for (const button of buttons) { button.setAttribute('aria-pressed', button.dataset.iconName === iconName); } } for (const button of buttons) { button.addEventListener('click', function (e) { updateValue(e.target.dataset.iconName); }) } updateValue(input.value); } export function setupCalendar(calendar) { const startDate = calendar.querySelector('input[name="start_date"]'); const endDate = calendar.querySelector('input[name="end_date"]'); const days = Array.from(calendar.querySelectorAll('time')); const dialog = calendar.querySelector('dialog'); const clear = function () { startDate.value = endDate.value = ""; days.forEach((e) => e.removeAttribute('aria-checked')); } dialog.addEventListener('close', clear); dialog.querySelector('button').addEventListener('click', function (e) { e.preventDefault(); dialog.close(); }); const selectDate = function (e) { e.preventDefault(); const date = e.currentTarget.dateTime; if (!date) { return; } if (!startDate.value) { startDate.value = date; e.currentTarget.setAttribute('aria-checked', true); return; } else if (startDate.value > date) { endDate.value = startDate.value; startDate.value = date; } else { endDate.value = date; } for (const day of days) { if (day.dateTime >= startDate.value && day.dateTime <= endDate.value) { day.setAttribute('aria-checked', true); } else { day.removeAttribute('aria-checked'); } } dialog.showModal(); } for (const day of days) { day.addEventListener('click', selectDate); } clear(); } htmx.onLoad((target) => { if (target.tagName === 'DIALOG') { target.showModal(); } }) htmx.onLoad((content) => { const sortables = Array.from(content.querySelectorAll('.sortable table tbody')); for (const sortable of sortables) { const sortableInstance = new Sortable(sortable, { animation: 150, draggable: '>tr', handle: '.handle', onMove: (evt) => evt.related.className.indexOf('htmx-indicator') === -1, onEnd: function () { this.option('disabled', true); }, }); sortable.addEventListener('htmx:afterSwap', function () { sortableInstance.option('disabled', false); }); } })