removing handwritten JS
This commit is contained in:
@@ -67,19 +67,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile: a backdrop behind an open navbar dropdown. It absorbs taps
|
/* Mobile: a dimming backdrop behind an open navbar dropdown, driven by
|
||||||
outside the menu — closing the dropdown rather than letting the tap
|
CSS alone. `:has()` shows it whenever a dropdown holds focus; a tap
|
||||||
reach the page — and dims the page to show the menu is modal. It sits
|
outside the menu blurs the trigger, which closes the dropdown. The
|
||||||
below the dropdown content (z-50) so the menu items stay tappable. */
|
delayed `visibility` transition keeps the backdrop hit-testable for a
|
||||||
|
beat after that tap, so the tap lands on the backdrop instead of
|
||||||
|
falling through to the page. It sits below the dropdown content
|
||||||
|
(z-50) so the menu items stay tappable. */
|
||||||
#nav-backdrop { display: none; }
|
#nav-backdrop { display: none; }
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
#nav-backdrop {
|
#nav-backdrop {
|
||||||
|
display: block;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
background-color: rgba(0, 0, 0, 0.25);
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.15s ease, visibility 0s linear 0.2s;
|
||||||
|
}
|
||||||
|
.navbar:has(.dropdown:focus-within) ~ #nav-backdrop {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transition: opacity 0.15s ease, visibility 0s;
|
||||||
}
|
}
|
||||||
#nav-backdrop.nav-backdrop--on { display: block; }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% block head %}{% endblock head %}
|
{% block head %}{% endblock head %}
|
||||||
@@ -139,11 +150,15 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- The language buttons submit this form; the /lang handler sets
|
||||||
|
the `lang` cookie and redirects back. The theme buttons are
|
||||||
|
type="button" so they never submit. -->
|
||||||
|
<form method="post" action="/lang">
|
||||||
<ul tabindex="0"
|
<ul tabindex="0"
|
||||||
class="menu dropdown-content z-50 mt-3 w-56 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg">
|
class="menu dropdown-content z-50 mt-3 w-56 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg">
|
||||||
<li class="menu-title">{{ t(key="settings-language", lang=lang) }}</li>
|
<li class="menu-title">{{ t(key="settings-language", lang=lang) }}</li>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" onclick="setLang('en')" class="{% if lang == 'en' %}active{% endif %}">
|
<button type="submit" name="lang" value="en" class="{% if lang == 'en' %}active{% endif %}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="h-4 w-4">
|
stroke="currentColor" class="h-4 w-4">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
@@ -159,7 +174,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button type="button" onclick="setLang('sk')" class="{% if lang == 'sk' %}active{% endif %}">
|
<button type="submit" name="lang" value="sk" class="{% if lang == 'sk' %}active{% endif %}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="h-4 w-4">
|
stroke="currentColor" class="h-4 w-4">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
@@ -218,6 +233,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -229,38 +245,6 @@
|
|||||||
{% block content %}{% endblock content %}
|
{% block content %}{% endblock content %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
|
||||||
function setLang(l) {
|
|
||||||
document.cookie = 'lang=' + l + ';path=/;max-age=31536000';
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile: while a navbar dropdown is open, a full-screen backdrop covers
|
|
||||||
// the page so a tap outside the menu only closes it, instead of also
|
|
||||||
// activating whatever sat underneath. Preventing the backdrop's mousedown
|
|
||||||
// keeps the trigger focused (menu open) until the tap's click lands on the
|
|
||||||
// backdrop itself, which then blurs the trigger to close the menu.
|
|
||||||
(function () {
|
|
||||||
var backdrop = document.getElementById('nav-backdrop');
|
|
||||||
if (!backdrop) return;
|
|
||||||
var hide = function () { backdrop.classList.remove('nav-backdrop--on'); };
|
|
||||||
document.querySelectorAll('.navbar .dropdown').forEach(function (dd) {
|
|
||||||
dd.addEventListener('focusin', function () {
|
|
||||||
backdrop.classList.add('nav-backdrop--on');
|
|
||||||
});
|
|
||||||
dd.addEventListener('focusout', function (e) {
|
|
||||||
if (!dd.contains(e.relatedTarget)) hide();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
backdrop.addEventListener('mousedown', function (e) { e.preventDefault(); });
|
|
||||||
backdrop.addEventListener('click', function () {
|
|
||||||
if (document.activeElement && document.activeElement.blur) {
|
|
||||||
document.activeElement.blur();
|
|
||||||
}
|
|
||||||
hide();
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
{% block js %}{% endblock js %}
|
{% block js %}{% endblock js %}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
//! court. Booked slots are coloured; free slots are blank. The same grid is
|
//! court. Booked slots are coloured; free slots are blank. The same grid is
|
||||||
//! reused by the admin dashboard with `is_admin = true`.
|
//! reused by the admin dashboard with `is_admin = true`.
|
||||||
|
|
||||||
|
use axum::http::{header, HeaderMap};
|
||||||
|
use axum::response::Redirect;
|
||||||
use axum_extra::extract::cookie::CookieJar;
|
use axum_extra::extract::cookie::CookieJar;
|
||||||
use chrono::{Datelike, Duration, NaiveDate, Utc};
|
use chrono::{Datelike, Duration, NaiveDate, Utc};
|
||||||
use loco_rs::prelude::*;
|
use loco_rs::prelude::*;
|
||||||
@@ -312,6 +314,46 @@ pub async fn index(
|
|||||||
format::render().view(&v, "calendar/week.html", &page)
|
format::render().view(&v, "calendar/week.html", &page)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn routes() -> Routes {
|
#[derive(Debug, Deserialize)]
|
||||||
Routes::new().add("/", get(index))
|
pub struct LangForm {
|
||||||
|
pub lang: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switches the UI language. The navbar's language buttons post here; the
|
||||||
|
/// `lang` cookie is set server-side and the visitor is bounced back to the
|
||||||
|
/// page they came from. This replaces the old client-side `setLang` script.
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_lang(headers: HeaderMap, Form(form): Form<LangForm>) -> Result<Response> {
|
||||||
|
// Only the two supported languages; anything else falls back to Slovak,
|
||||||
|
// matching `current_lang`.
|
||||||
|
let lang = if form.lang == "en" { "en" } else { "sk" };
|
||||||
|
let cookie = format!("lang={lang}; Path=/; Max-Age=31536000; SameSite=Lax");
|
||||||
|
Ok((
|
||||||
|
[(header::SET_COOKIE, cookie)],
|
||||||
|
Redirect::to(&back_path(&headers)),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On-site path of the page that submitted the form, read from `Referer`.
|
||||||
|
/// Scheme and host are stripped so a stale or foreign header can only ever
|
||||||
|
/// bounce the visitor to a path on this site, never off it.
|
||||||
|
fn back_path(headers: &HeaderMap) -> String {
|
||||||
|
let raw = headers
|
||||||
|
.get(header::REFERER)
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("/");
|
||||||
|
match raw.split_once("://") {
|
||||||
|
Some((_, rest)) => match rest.find('/') {
|
||||||
|
Some(i) => rest[i..].to_string(),
|
||||||
|
None => "/".to_string(),
|
||||||
|
},
|
||||||
|
None => raw.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Routes {
|
||||||
|
Routes::new()
|
||||||
|
.add("/", get(index))
|
||||||
|
.add("/lang", post(set_lang))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user