From 25c22e71076f96d4a2023248404c6efe73366837 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 2 Apr 2026 16:08:07 +0000 Subject: [PATCH] v2 with admin changes --- core/__pycache__/views.cpython-311.pyc | Bin 13512 -> 14266 bytes core/templates/core/calendar_embed.html | 4 +- core/templates/core/event_dashboard.html | 69 +++++++++++++++++++++++ core/tests.py | 6 ++ core/views.py | 13 ++++- static/css/custom.css | 21 +++++++ static/js/calendar.js | 47 +++++++++++++++ staticfiles/css/custom.css | 21 +++++++ staticfiles/js/calendar.js | 47 +++++++++++++++ 9 files changed, 226 insertions(+), 2 deletions(-) diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 148300c0f53524e7606e0d0d573b660c62874813..f3bcfc60611458e431efd0239b8358d214a8f317 100644 GIT binary patch delta 2015 zcmai#eQXp(6u@_OZ|`e+*DJKWejIn(YkR$IOM8^cmxb04FfoJ>Dhf@sTstcqmNv{? zDItdxQqk}s!lS^`vQ zDX>ZubO-fLmhmm5PQvzJz8xdMqYH!M1SW)P7U~-j#&L-SfzVa^6wPH%XojdCmaU@! zV3?{vjaC8cX%N^zNvELI{E9Pg)l$EcKZ?D*iwlELhAKh*b2GmwFB20IztI>z3O3@_ zth+bbr}xt+>(=-8=u{K4OL_)leY88;6CdasOz7Q%EXG`5JwdWIrXLv8$3b02mf#l_mF-+8L$xlReW-8`7cnOsGArbYxnUj%iB=3xm>Q z&RRfVINJf83qn%Z2d!Dg2(M}?JxaxK5e^cnS*Y|n8VIl<%hs%QuO6j3%aRpY5`pbV z&u@B>y#&MTAmCBYmvqPX4h-epH?7zksEB&Y0EY9YpFP`tvi;(kvCg!jWfbk2t#d|+ zOj{!Sr_vC=Wpjwbg#TRAz+1c%VxolqV5@K^ykMO#`wDneD&-p;-lg)S_3@w>bMv%R z;{AW}{o{2%W#MXV5JVpPS+-6!u7$aA26T>9Ai4k(A{&B+kCs(CPeI2xr3wCh+0N~AmG#$Frz=-x zDp$^_;f&gnR$DV_>pit?Ms541Xj*Mcs~a=w#(V0P8FkB@m(%KwjJhMISRKxJ0q_tp zZ*vw{9}0k+;7n~OpAyM&{#)gG>k0JWE#B-aC1?0%-x@2r!Ors$UyXbTx^JhZe3PQ} zEK1Mt?!a>EJJ@}fpAU?_a@^2G>_%|Jdx-ZD9{{u>X0Ct&2LhisD?(gAco1h07ZD#K zY>1EeX*J+6ylKJj-H7x2Yt=7*4Be}#`)Ze1VfqE%hh6GWG_F#;PfzHyiJjuxtD8Fw z2hmb<{f{3JP|8|O^SlEfVG5!Ef$^wW*bvWDuad9A$L&;M%|SxW@)NZo*A*PK=hxt0 z*4B_Ae!sTTX&e?}mP?@|Ih`5~Ifxmcm7Nl`C8bLOwU0_iERRNOQlO4RIauWjEl6Ob zlTy+`mCG_e(BLI5zB(M_vG6$Du}5oH+$;vB>^906hX-_!g z38x0?*O3i)=!Xlp4fgcKdew!*#->0Di!6F#W`jj1@(+OB;13!Catf6H=1SuM1r>}$ zbWPyLLa*{&4MjF9m8d15;3KzC`LcwY=B3Nm#3Y7mXYIy=q&m$OJ(*h!Kdevn!^}OO z!!&UbpqlxWC7^G>3Q0G<2R0w>2+r9{a)Rt|&UzbXOUmc`k(=JMe{IIUHfIB=F_g0l zg}$6);d9>Rz-I`^369h+%bf(SXue4!CwXPFPyPZ{DNeOD*AQ}rM`%!JV$cEBeWB;0tI>41<^C?zgRa-f@u=WkYG-96W9P?UJ}IWMM;p~U3t4u!Ldd7 I_-JdxzX#9cmjD0& delta 1510 zcmai!ZA@Eb6oBu0Z(G`1N=u~n18B=F7TN+E)D1JgemG+MVdjF-jcfwry=EkQ?d=`T zrOY{Delc-7GdjmUlQAosCN|N9_>uTa|BzuwY#JBi5C551=KN>WbM77BG>bQRpPsMx zJ?B05o-T}jGgx)m<54AircW#mdgI5df^6Y<)8{WW@Ovg%PlVS@N$jT{eASqSaxm|_VxL3PyaIjgA?AQ5!kgrz z0yAMvPO4B0Z*k}4Qiag>?bovgbkxSxLwLXlu51>1Yj@O@D{L-xMMB&ntW-EF9k(Pz zS#mS{*Olhr-mq~mDc57#LkCQk>=p!5@yh?bD@H!${E$+-Ms1>4`@CLyu zfuuJH(}W7bF?co}^_gXLklsg_f}`<>dIH7wab;KF$3$o0=XgX8TLq%=peOc1e>TST zA>GhYmZi4EOmrG4m5dj6-NjTlsNSle$`f)?Q>R1yRtX%iE0F0tAEE-KdwrNlU6 zM__w%!he#y&VS{A)=bEiSA=yNY_i`i%r+7hVX-+Uf5Kq6^*NZ0Rl=vqv$mXcUWM(h z8rcYdCme>4TfN;1cjlDS5_g?ZCTvCsx88no5?77dRZWt($EaJ^ax#~=drDs4kdt#t z&c-We?Qm>EfK|Z{Da}`^8t5O=M|i*J)ra@!9R5tr;n#C1{ixe32JyI(@_5(N@N2pm zb_GK1>%9=e=uFzp%auICqa()N-T|b3zz}EACC(Af!;>u`l}7XiuIw)`*7AI~=nH)B zOMT}{UG2P{d2A`YbveCt$=9{)>ni-zvV}cDZ|$J>sr`F~1_xrR<%tVu&D+hI#6^_F zGlaEwlb5YNUxJ-&QTvDJE5k(FK6Pydr(vjd%;n$;w{iQdj6K-)pe!g?$!T7l>)cEt zKA?m1h^&l=#YBV*r&sX@d3NDV8Xj_27oFasQ@i2xmuVV{ftI2_RJNmN>Sc%IwI;9m+fI7pK9V;|! w>Sjqv_8pGA*L0m}OH5m4T3N1S_;-U?Q6xFOW{JR;o4@v|
-
+
+ {% if show_embed_header %}
Embeddable widget @@ -14,6 +15,7 @@
Open full page
+ {% endif %} {% include "core/includes/calendar_widget.html" with calendar_variant="embed" %}
diff --git a/core/templates/core/event_dashboard.html b/core/templates/core/event_dashboard.html index 7eadcb2..a88cf28 100644 --- a/core/templates/core/event_dashboard.html +++ b/core/templates/core/event_dashboard.html @@ -17,6 +17,75 @@
+
+
+
+ Embed settings +

Generate a copy-ready widget snippet

+

Tune the iframe once, copy the finished code, and paste it into your existing HTML site.

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
+
+ Live embed output +

Copy either the direct widget URL or the full iframe snippet.

+
+
+ Preview widget + + +
+
+ +
+
+ Widget URL +
{{ embed_base_url }}
+
+
+ Iframe snippet +
<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>
+
+
+
+
+
+
diff --git a/core/tests.py b/core/tests.py index 20438a9..60b2f28 100644 --- a/core/tests.py +++ b/core/tests.py @@ -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): diff --git a/core/views.py b/core/views.py index a5bd905..47f241f 100644 --- a/core/views.py +++ b/core/views.py @@ -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'' 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, ), ) diff --git a/static/css/custom.css b/static/css/custom.css index 75d37a1..a41b0a1 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -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); diff --git a/static/js/calendar.js b/static/js/calendar.js index 0d7c6c2..08f3444 100644 --- a/static/js/calendar.js +++ b/static/js/calendar.js @@ -103,4 +103,51 @@ document.addEventListener('DOMContentLoaded', () => { } }); }); + + const escapeAttribute = (value) => String(value || '') + .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 = ``; + + urlOutput.textContent = widgetUrl; + iframeOutput.textContent = iframeSnippet; + if (previewLink) { + previewLink.href = widgetUrl; + } + }; + + ['input', 'change'].forEach((eventName) => { + form.addEventListener(eventName, updateEmbedOutput); + }); + updateEmbedOutput(); + }); + }); diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 75d37a1..a41b0a1 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -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); diff --git a/staticfiles/js/calendar.js b/staticfiles/js/calendar.js index 0d7c6c2..08f3444 100644 --- a/staticfiles/js/calendar.js +++ b/staticfiles/js/calendar.js @@ -103,4 +103,51 @@ document.addEventListener('DOMContentLoaded', () => { } }); }); + + const escapeAttribute = (value) => String(value || '') + .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 = ``; + + urlOutput.textContent = widgetUrl; + iframeOutput.textContent = iframeSnippet; + if (previewLink) { + previewLink.href = widgetUrl; + } + }; + + ['input', 'change'].forEach((eventName) => { + form.addEventListener(eventName, updateEmbedOutput); + }); + updateEmbedOutput(); + }); + });