v2 with admin changes
This commit is contained in:
parent
693107e079
commit
25c22e7107
Binary file not shown.
@ -5,7 +5,8 @@
|
||||
{% block content %}
|
||||
<section class="embed-section">
|
||||
<div class="container-fluid px-md-4 px-lg-5">
|
||||
<div class="embed-shell-card">
|
||||
<div class="embed-shell-card {% if not show_embed_header %}embed-shell-card-compact{% endif %}">
|
||||
{% if show_embed_header %}
|
||||
<div class="embed-header d-flex flex-column flex-md-row justify-content-between gap-3 align-items-md-center">
|
||||
<div>
|
||||
<span class="eyebrow-pill">Embeddable widget</span>
|
||||
@ -14,6 +15,7 @@
|
||||
</div>
|
||||
<a class="btn btn-ghost" href="{% url 'calendar_page' %}" target="_blank" rel="noopener">Open full page</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "core/includes/calendar_widget.html" with calendar_variant="embed" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -17,6 +17,75 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-xl-5">
|
||||
<div class="card-surface embed-settings-card h-100">
|
||||
<span class="eyebrow-pill">Embed settings</span>
|
||||
<h2 class="h4 mt-3 mb-2">Generate a copy-ready widget snippet</h2>
|
||||
<p class="section-copy">Tune the iframe once, copy the finished code, and paste it into your existing HTML site.</p>
|
||||
|
||||
<form class="embed-settings-form" data-embed-settings data-embed-base-url="{{ embed_base_url }}">
|
||||
<div class="row g-3">
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="embedTitle">Iframe title</label>
|
||||
<input class="form-control" id="embedTitle" name="title" type="text" value="Where to find us calendar">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="embedMonth">Starting month</label>
|
||||
<input class="form-control" id="embedMonth" name="month" type="month" value="{{ default_embed_month }}">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="embedWidth">Width</label>
|
||||
<input class="form-control" id="embedWidth" name="width" type="text" value="100%">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="embedHeight">Height (px)</label>
|
||||
<input class="form-control" id="embedHeight" name="height" type="number" min="360" step="10" value="760">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="embedRadius">Corner radius (px)</label>
|
||||
<input class="form-control" id="embedRadius" name="radius" type="number" min="0" step="2" value="24">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label" for="embedHeader">Widget header</label>
|
||||
<select class="form-select" id="embedHeader" name="header">
|
||||
<option value="1" selected>Show header and action</option>
|
||||
<option value="0">Hide header for a tighter embed</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-7">
|
||||
<div class="card-surface code-card h-100">
|
||||
<div class="code-card-header d-flex flex-column flex-lg-row justify-content-between gap-3 align-items-lg-center">
|
||||
<div>
|
||||
<strong>Live embed output</strong>
|
||||
<p class="mb-0 text-white-50 small">Copy either the direct widget URL or the full iframe snippet.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<a class="btn btn-sm btn-primary-brand" href="{{ embed_base_url }}" target="_blank" rel="noopener" data-embed-preview>Preview widget</a>
|
||||
<button class="btn btn-sm btn-ghost" type="button" data-copy-snippet data-copy-target="#dashboardEmbedUrl">Copy URL</button>
|
||||
<button class="btn btn-sm btn-ghost" type="button" data-copy-snippet data-copy-target="#dashboardIframeSnippet">Copy iframe</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="embed-output-stack mt-4">
|
||||
<div>
|
||||
<small class="text-uppercase text-white-50 d-block mb-2">Widget URL</small>
|
||||
<pre id="dashboardEmbedUrl" class="code-snippet mb-0">{{ embed_base_url }}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<small class="text-uppercase text-white-50 d-block mb-2">Iframe snippet</small>
|
||||
<pre id="dashboardIframeSnippet" class="code-snippet mb-0"><iframe src="{{ embed_base_url }}" title="Where to find us calendar" width="100%" height="760" style="border:0;border-radius:24px;overflow:hidden;" loading="lazy"></iframe></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-card card-surface">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between gap-3 align-items-md-center mb-3">
|
||||
<div>
|
||||
|
||||
@ -45,6 +45,12 @@ class CalendarViewTests(TestCase):
|
||||
response = client.get(reverse('event_dashboard'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Manage your public event calendar securely')
|
||||
self.assertContains(response, 'Generate a copy-ready widget snippet')
|
||||
|
||||
def test_embed_page_can_hide_header(self):
|
||||
response = self.client.get(reverse('calendar_embed') + '?header=0')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, 'Open full page')
|
||||
|
||||
|
||||
class EventDashboardMutationTests(TestCase):
|
||||
|
||||
@ -107,6 +107,14 @@ def _base_context(**extra):
|
||||
return context
|
||||
|
||||
|
||||
def _embed_base_url(request):
|
||||
return request.build_absolute_uri(reverse('calendar_embed'))
|
||||
|
||||
|
||||
def _show_embed_header(request):
|
||||
return request.GET.get('header', '1') != '0'
|
||||
|
||||
|
||||
@login_required(login_url='login')
|
||||
def event_dashboard(request):
|
||||
if not request.user.is_staff:
|
||||
@ -119,6 +127,8 @@ def event_dashboard(request):
|
||||
page_title='Manage events',
|
||||
events=events,
|
||||
dashboard_count=events.count(),
|
||||
embed_base_url=_embed_base_url(request),
|
||||
default_embed_month=timezone.localdate().replace(day=1).strftime('%Y-%m'),
|
||||
),
|
||||
)
|
||||
|
||||
@ -221,7 +231,7 @@ def event_dashboard_detail(request, slug):
|
||||
def home(request):
|
||||
month_context = _build_month_context(request.GET.get('month'))
|
||||
upcoming_events = list(Event.objects.filter(is_published=True, end__gte=timezone.now()).order_by('start', 'name')[:6])
|
||||
embed_url = request.build_absolute_uri(reverse('calendar_embed'))
|
||||
embed_url = _embed_base_url(request)
|
||||
iframe_snippet = f'<iframe src="{embed_url}" title="Where to find us calendar" width="100%" height="760" style="border:0;border-radius:24px;overflow:hidden;"></iframe>'
|
||||
return render(
|
||||
request,
|
||||
@ -257,6 +267,7 @@ def calendar_embed(request):
|
||||
_base_context(
|
||||
page_title='Embeddable calendar',
|
||||
embedded=True,
|
||||
show_embed_header=_show_embed_header(request),
|
||||
**month_context,
|
||||
),
|
||||
)
|
||||
|
||||
@ -670,6 +670,24 @@ span {
|
||||
color: #fff2df;
|
||||
}
|
||||
|
||||
.embed-settings-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.embed-settings-form .form-label {
|
||||
font-weight: 700;
|
||||
color: var(--brand-ink);
|
||||
}
|
||||
|
||||
.embed-output-stack {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.embed-shell-card-compact {
|
||||
padding-top: 0.8rem;
|
||||
}
|
||||
|
||||
.dashboard-banner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -693,9 +711,12 @@ span {
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="url"],
|
||||
input[type="month"],
|
||||
input[type="number"],
|
||||
input[type="datetime-local"],
|
||||
textarea,
|
||||
.form-control,
|
||||
.form-select,
|
||||
.form-check-input {
|
||||
border-radius: 16px;
|
||||
border-color: rgba(18, 32, 35, 0.12);
|
||||
|
||||
@ -103,4 +103,51 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const escapeAttribute = (value) => String(value || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
|
||||
document.querySelectorAll('[data-embed-settings]').forEach((form) => {
|
||||
const baseUrl = form.dataset.embedBaseUrl || '';
|
||||
const urlOutput = document.getElementById('dashboardEmbedUrl');
|
||||
const iframeOutput = document.getElementById('dashboardIframeSnippet');
|
||||
const previewLink = document.querySelector('[data-embed-preview]');
|
||||
if (!baseUrl || !urlOutput || !iframeOutput) return;
|
||||
|
||||
const updateEmbedOutput = () => {
|
||||
const formData = new FormData(form);
|
||||
const params = new URLSearchParams();
|
||||
const month = String(formData.get('month') || '').trim();
|
||||
const header = String(formData.get('header') || '1');
|
||||
const title = String(formData.get('title') || 'Where to find us calendar').trim() || 'Where to find us calendar';
|
||||
const width = String(formData.get('width') || '100%').trim() || '100%';
|
||||
const height = Math.max(parseInt(String(formData.get('height') || '760'), 10) || 760, 360);
|
||||
const radius = Math.max(parseInt(String(formData.get('radius') || '24'), 10) || 24, 0);
|
||||
|
||||
if (/^\d{4}-\d{2}$/.test(month)) {
|
||||
params.set('month', month);
|
||||
}
|
||||
if (header === '0') {
|
||||
params.set('header', '0');
|
||||
}
|
||||
|
||||
const widgetUrl = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
|
||||
const iframeSnippet = `<iframe src="${escapeAttribute(widgetUrl)}" title="${escapeAttribute(title)}" width="${escapeAttribute(width)}" height="${height}" style="border:0;border-radius:${radius}px;overflow:hidden;" loading="lazy"></iframe>`;
|
||||
|
||||
urlOutput.textContent = widgetUrl;
|
||||
iframeOutput.textContent = iframeSnippet;
|
||||
if (previewLink) {
|
||||
previewLink.href = widgetUrl;
|
||||
}
|
||||
};
|
||||
|
||||
['input', 'change'].forEach((eventName) => {
|
||||
form.addEventListener(eventName, updateEmbedOutput);
|
||||
});
|
||||
updateEmbedOutput();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -670,6 +670,24 @@ span {
|
||||
color: #fff2df;
|
||||
}
|
||||
|
||||
.embed-settings-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.embed-settings-form .form-label {
|
||||
font-weight: 700;
|
||||
color: var(--brand-ink);
|
||||
}
|
||||
|
||||
.embed-output-stack {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.embed-shell-card-compact {
|
||||
padding-top: 0.8rem;
|
||||
}
|
||||
|
||||
.dashboard-banner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -693,9 +711,12 @@ span {
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="url"],
|
||||
input[type="month"],
|
||||
input[type="number"],
|
||||
input[type="datetime-local"],
|
||||
textarea,
|
||||
.form-control,
|
||||
.form-select,
|
||||
.form-check-input {
|
||||
border-radius: 16px;
|
||||
border-color: rgba(18, 32, 35, 0.12);
|
||||
|
||||
@ -103,4 +103,51 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const escapeAttribute = (value) => String(value || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
|
||||
document.querySelectorAll('[data-embed-settings]').forEach((form) => {
|
||||
const baseUrl = form.dataset.embedBaseUrl || '';
|
||||
const urlOutput = document.getElementById('dashboardEmbedUrl');
|
||||
const iframeOutput = document.getElementById('dashboardIframeSnippet');
|
||||
const previewLink = document.querySelector('[data-embed-preview]');
|
||||
if (!baseUrl || !urlOutput || !iframeOutput) return;
|
||||
|
||||
const updateEmbedOutput = () => {
|
||||
const formData = new FormData(form);
|
||||
const params = new URLSearchParams();
|
||||
const month = String(formData.get('month') || '').trim();
|
||||
const header = String(formData.get('header') || '1');
|
||||
const title = String(formData.get('title') || 'Where to find us calendar').trim() || 'Where to find us calendar';
|
||||
const width = String(formData.get('width') || '100%').trim() || '100%';
|
||||
const height = Math.max(parseInt(String(formData.get('height') || '760'), 10) || 760, 360);
|
||||
const radius = Math.max(parseInt(String(formData.get('radius') || '24'), 10) || 24, 0);
|
||||
|
||||
if (/^\d{4}-\d{2}$/.test(month)) {
|
||||
params.set('month', month);
|
||||
}
|
||||
if (header === '0') {
|
||||
params.set('header', '0');
|
||||
}
|
||||
|
||||
const widgetUrl = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl;
|
||||
const iframeSnippet = `<iframe src="${escapeAttribute(widgetUrl)}" title="${escapeAttribute(title)}" width="${escapeAttribute(width)}" height="${height}" style="border:0;border-radius:${radius}px;overflow:hidden;" loading="lazy"></iframe>`;
|
||||
|
||||
urlOutput.textContent = widgetUrl;
|
||||
iframeOutput.textContent = iframeSnippet;
|
||||
if (previewLink) {
|
||||
previewLink.href = widgetUrl;
|
||||
}
|
||||
};
|
||||
|
||||
['input', 'change'].forEach((eventName) => {
|
||||
form.addEventListener(eventName, updateEmbedOutput);
|
||||
});
|
||||
updateEmbedOutput();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user