From b46320ea94a782edc25c9ffa8b19008070fe1489 Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Sun, 5 Jul 2026 12:12:48 +0200 Subject: [PATCH] add a duplicate button to event occurrences --- dnscms/dnscms/static/js/page-editor.js | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/dnscms/dnscms/static/js/page-editor.js b/dnscms/dnscms/static/js/page-editor.js index 72c621c..89c691f 100644 --- a/dnscms/dnscms/static/js/page-editor.js +++ b/dnscms/dnscms/static/js/page-editor.js @@ -16,6 +16,76 @@ function registerPricingPanelToggler() { } } +// Adds a "Duplicate" button to each event occurrence ("Forekomst") row, copying +// its date, time and venue. Wagtail's InlinePanel has no native duplicate. +function registerOccurrenceCloning() { + const FORMSET_PREFIX = "occurrences"; + const container = document.getElementById(`id_${FORMSET_PREFIX}-FORMS`); + if (!container) return; // not an event page + + const FIELDS = ["start", "end", "venue", "venue_custom"]; + const duplicateIcon = + ''; + + function duplicateRow(sourceChild) { + const addButton = document.getElementById(`id_${FORMSET_PREFIX}-ADD`); + const totalForms = document.getElementById( + `id_${FORMSET_PREFIX}-TOTAL_FORMS` + ); + if (!addButton || !totalForms) return; + + // `inline_child_occurrences-0` -> form prefix `occurrences-0`, used by field ids. + const sourcePrefix = sourceChild.id.replace(/^inline_child_/, ""); + const values = {}; + FIELDS.forEach((name) => { + const field = document.getElementById(`id_${sourcePrefix}-${name}`); + values[name] = field ? field.value : ""; + }); + + // The new form takes the current TOTAL_FORMS index. Click Wagtail's real + // "Add" so it appends and initialises the row (e.g. date/time pickers). + const newIndex = parseInt(totalForms.value, 10); + addButton.click(); + + const newPrefix = `${FORMSET_PREFIX}-${newIndex}`; + FIELDS.forEach((name) => { + const field = document.getElementById(`id_${newPrefix}-${name}`); + if (!field) return; + field.value = values[name]; + field.dispatchEvent(new Event("input", { bubbles: true })); + field.dispatchEvent(new Event("change", { bubbles: true })); + }); + } + + function addDuplicateButton(child) { + const controls = child.querySelector("[data-panel-controls]"); + if (!controls || controls.querySelector("[data-duplicate-occurrence]")) return; + + const button = document.createElement("button"); + button.type = "button"; + button.className = "button button--icon text-replace white"; + button.title = "Duplicate"; + button.setAttribute("data-duplicate-occurrence", ""); + button.innerHTML = duplicateIcon; + button.addEventListener("click", () => duplicateRow(child)); + + // Sit just before the delete button, matching the native control order. + const deleteButton = controls.querySelector('[id$="-DELETE-button"]'); + controls.insertBefore(button, deleteButton); + } + + function refresh() { + container + .querySelectorAll("[data-inline-panel-child]") + .forEach((child) => addDuplicateButton(child)); + } + + // Cover existing rows plus any added later (via "Add" or "Duplicate"). + refresh(); + new MutationObserver(() => refresh()).observe(container, { childList: true }); +} + document.addEventListener("DOMContentLoaded", () => { registerPricingPanelToggler(); + registerOccurrenceCloning(); });