Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93956435cf
|
||
|
|
39364b5023
|
||
|
|
b46320ea94
|
@@ -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 =
|
||||||
|
'<svg class="icon icon-copy" aria-hidden="true"><use href="#icon-copy"></use></svg>';
|
||||||
|
|
||||||
|
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", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
registerPricingPanelToggler();
|
registerPricingPanelToggler();
|
||||||
|
registerOccurrenceCloning();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ export async function generateMetadata(
|
|||||||
return getSeoMetadata(data.index as EventIndexFragment, parent);
|
return getSeoMetadata(data.index as EventIndexFragment, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
||||||
|
}) {
|
||||||
|
await searchParams;
|
||||||
const props = await loadEventIndexProps();
|
const props = await loadEventIndexProps();
|
||||||
return <EventIndexView {...props} />;
|
return <EventIndexView {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Suspense } from "react";
|
|
||||||
import { VenueFragment } from "@/gql/graphql";
|
import { VenueFragment } from "@/gql/graphql";
|
||||||
import { getClient } from "@/app/client";
|
import { getClient } from "@/app/client";
|
||||||
import { EventContainer } from "@/components/events/EventContainer";
|
import { EventContainer } from "@/components/events/EventContainer";
|
||||||
@@ -46,14 +45,12 @@ export function EventIndexView({
|
|||||||
return (
|
return (
|
||||||
<main className="site-main" id="main">
|
<main className="site-main" id="main">
|
||||||
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
|
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
|
||||||
<Suspense>
|
<EventContainer
|
||||||
<EventContainer
|
events={events}
|
||||||
events={events}
|
eventCategories={eventCategories}
|
||||||
eventCategories={eventCategories}
|
eventOrganizers={eventOrganizers}
|
||||||
eventOrganizers={eventOrganizers}
|
venues={venues}
|
||||||
venues={venues}
|
/>
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
.events {
|
||||||
|
// keep the footer below the fold while the event list renders
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
.eventWrapper {
|
.eventWrapper {
|
||||||
//margin: calc(var(--spacing-sitepadding-block)*-1) calc(var(--spacing-sitepadding-inline)*-1);
|
//margin: calc(var(--spacing-sitepadding-block)*-1) calc(var(--spacing-sitepadding-inline)*-1);
|
||||||
}
|
}
|
||||||
@@ -213,4 +218,4 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fullMenu {
|
.fullMenu {
|
||||||
right: 0;
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggleMenu {
|
.toggleMenu {
|
||||||
@@ -137,18 +137,19 @@
|
|||||||
|
|
||||||
.fullMenu {
|
.fullMenu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: -100%;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
transform: translateX(100%);
|
||||||
|
|
||||||
background: var(--color-menu-background);
|
background: var(--color-menu-background);
|
||||||
color: var(--color-menu-text);
|
color: var(--color-menu-text);
|
||||||
padding: var(--spacing-sitepadding-block);
|
padding: var(--spacing-sitepadding-block);
|
||||||
|
|
||||||
transition: all var(--transition-easing);
|
transition: transform var(--transition-easing);
|
||||||
|
|
||||||
a {
|
a {
|
||||||
--size-icon: var(--font-size-caption);
|
--size-icon: var(--font-size-caption);
|
||||||
|
|||||||
Reference in New Issue
Block a user