build(deps): bump askama from 0.12.1 to 0.15.4 (#7)

* build(deps): bump askama from 0.12.1 to 0.15.4

Bumps [askama](https://github.com/askama-rs/askama) from 0.12.1 to 0.15.4.
- [Release notes](https://github.com/askama-rs/askama/releases)
- [Commits](https://github.com/askama-rs/askama/compare/0.12.1...v0.15.4)

---
updated-dependencies:
- dependency-name: askama
  dependency-version: 0.15.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(deps): adapt templates and tests for askama 0.15

- Migrate macro calls from {% call %} to {{ }} expression syntax (134 occurrences)
- Update test assertion for askama 0.15's numeric HTML entity encoding (&#38; vs &amp;)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jon Seager <jon@sgrs.uk>
This commit is contained in:
dependabot[bot] 2026-02-06 18:45:34 +00:00 committed by GitHub
parent 5bba529936
commit ea4093b699
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 166 additions and 174 deletions

74
Cargo.lock generated
View file

@ -103,46 +103,54 @@ checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]] [[package]]
name = "askama" name = "askama"
version = "0.12.1" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" checksum = "08e1676b346cadfec169374f949d7490fd80a24193d37d2afce0c047cf695e57"
dependencies = [ dependencies = [
"askama_derive", "askama_macros",
"askama_escape", "itoa",
"humansize",
"num-traits",
"percent-encoding", "percent-encoding",
"serde",
"serde_json",
] ]
[[package]] [[package]]
name = "askama_derive" name = "askama_derive"
version = "0.12.5" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" checksum = "7661ff56517787343f376f75db037426facd7c8d3049cef8911f1e75016f3a37"
dependencies = [ dependencies = [
"askama_parser", "askama_parser",
"basic-toml", "basic-toml",
"mime", "memchr",
"mime_guess",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc-hash",
"serde", "serde",
"serde_derive",
"syn 2.0.114", "syn 2.0.114",
] ]
[[package]] [[package]]
name = "askama_escape" name = "askama_macros"
version = "0.10.3" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" checksum = "713ee4dbfd1eb719c2dab859465b01fa1d21cb566684614a713a6b7a99a4e47b"
dependencies = [
"askama_derive",
]
[[package]] [[package]]
name = "askama_parser" name = "askama_parser"
version = "0.2.1" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" checksum = "1d62d674238a526418b30c0def480d5beadb9d8964e7f38d635b03bf639c704c"
dependencies = [ dependencies = [
"nom", "rustc-hash",
"serde",
"serde_derive",
"unicode-ident",
"winnow",
] ]
[[package]] [[package]]
@ -1149,15 +1157,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.8.1"
@ -1543,16 +1542,6 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@ -3096,12 +3085,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.18" version = "0.3.18"
@ -3724,6 +3707,15 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "wiremock" name = "wiremock"
version = "0.6.5" version = "0.6.5"

View file

@ -7,7 +7,7 @@ edition = "2024"
anyhow = "1.0" anyhow = "1.0"
async-trait = "0.1" async-trait = "0.1"
axum = { version = "0.7", features = ["macros"] } axum = { version = "0.7", features = ["macros"] }
askama = "0.12" askama = "0.15"
base64 = "0.22" base64 = "0.22"
chrono = { version = "0.4", features = ["serde", "clock"] } chrono = { version = "0.4", features = ["serde", "clock"] }
clap = { version = "4.5", features = ["derive", "env"] } clap = { version = "4.5", features = ["derive", "env"] }

View file

@ -21,7 +21,7 @@
{% for passkey in passkeys %} {% for passkey in passkeys %}
<div class="flex items-center justify-between gap-4 rounded-md bg-surface-alt px-4 py-3"> <div class="flex items-center justify-between gap-4 rounded-md bg-surface-alt px-4 py-3">
<div class="flex items-center gap-3 min-w-0"> <div class="flex items-center gap-3 min-w-0">
{% call icons::key("h-4 w-4 text-accent shrink-0") %} {{ icons::key("h-4 w-4 text-accent shrink-0") }}
<div class="min-w-0"> <div class="min-w-0">
<span class="block text-sm font-semibold text-text">{{ passkey.name }}</span> <span class="block text-sm font-semibold text-text">{{ passkey.name }}</span>
<span class="block text-xs text-text-muted"> <span class="block text-xs text-text-muted">
@ -37,7 +37,7 @@
onclick="deletePasskey({{ passkey.id }}, '{{ passkey.name }}')" onclick="deletePasskey({{ passkey.id }}, '{{ passkey.name }}')"
aria-label="Delete passkey" aria-label="Delete passkey"
> >
{% call icons::delete("h-4 w-4") %} {{ icons::delete("h-4 w-4") }}
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -80,7 +80,7 @@
</button> </button>
</div> </div>
<div id="add-passkey-loading" class="mt-3 hidden flex items-center gap-3 text-sm text-accent"> <div id="add-passkey-loading" class="mt-3 hidden flex items-center gap-3 text-sm text-accent">
{% call icons::spinner("h-5 w-5") %} {{ icons::spinner("h-5 w-5") }}
Follow the prompts from your browser or device... Follow the prompts from your browser or device...
</div> </div>
</div> </div>
@ -132,7 +132,7 @@
onclick="revokeToken({{ token.id }}, '{{ token.name }}')" onclick="revokeToken({{ token.id }}, '{{ token.name }}')"
aria-label="Revoke token" aria-label="Revoke token"
> >
{% call icons::delete("h-4 w-4") %} {{ icons::delete("h-4 w-4") }}
</button> </button>
</div> </div>
{% endfor %} {% endfor %}

View file

@ -2,7 +2,7 @@
{% block head %} {% block head %}
<script> <script>
{% call location::location_search_js() %} {{ location::location_search_js() }}
</script> </script>
{% endblock %} {% endblock %}
@ -35,7 +35,7 @@
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/extract-bag-scan', {contentType: 'form'})" data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/extract-bag-scan', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_scanExtracted = true; document.getElementById('scan-extract-form').reset() } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Extraction failed. Please try again.' }" data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_scanExtracted = true; document.getElementById('scan-extract-form').reset() } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Extraction failed. Please try again.' }"
> >
{% call scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError", "Describe the coffee bag…", "Waiting for response…") %} {{ scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError", "Describe the coffee bag…", "Waiting for response…") }}
</form> </form>
</div> </div>
@ -427,14 +427,14 @@
<!-- Location search --> <!-- Location search -->
<div class="mt-4 border-b pb-4" data-show="!$_reviewingCafe"> <div class="mt-4 border-b pb-4" data-show="!$_reviewingCafe">
{% call location::location_search("$_cafeError") %} {{ location::location_search("$_cafeError") }}
<p data-show="$_cafeError" data-text="$_cafeError" style="display:none" class="mt-2 text-sm text-red-600"></p> <p data-show="$_cafeError" data-text="$_cafeError" style="display:none" class="mt-2 text-sm text-red-600"></p>
</div> </div>
<!-- Selected cafe indicator --> <!-- Selected cafe indicator -->
<div class="mt-4 border-b pb-4 flex items-center justify-between" data-show="$_reviewingCafe" style="display:none"> <div class="mt-4 border-b pb-4 flex items-center justify-between" data-show="$_reviewingCafe" style="display:none">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{% call icons::location("h-4 w-4 text-accent shrink-0") %} {{ icons::location("h-4 w-4 text-accent shrink-0") }}
<span class="text-sm font-medium text-text" data-text="$_cafeName"></span> <span class="text-sm font-medium text-text" data-text="$_cafeName"></span>
<span class="text-xs text-text-muted" data-show="$_cafeCity" data-text="$_cafeCity" style="display:none"></span> <span class="text-xs text-text-muted" data-show="$_cafeCity" data-text="$_cafeCity" style="display:none"></span>
</div> </div>

View file

@ -2,7 +2,7 @@
{% block head %} {% block head %}
<script> <script>
{% call location::location_search_js() %} {{ location::location_search_js() }}
</script> </script>
{% endblock %} {% endblock %}
@ -88,7 +88,7 @@
style="display: none" style="display: none"
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{% call icons::location("h-4 w-4 text-accent shrink-0") %} {{ icons::location("h-4 w-4 text-accent shrink-0") }}
<span class="font-medium text-text" data-text="$_cafeName"></span> <span class="font-medium text-text" data-text="$_cafeName"></span>
<span class="text-xs text-text-muted" data-show="$_cafeCity" data-text="$_cafeCity" style="display: none"></span> <span class="text-xs text-text-muted" data-show="$_cafeCity" data-text="$_cafeCity" style="display: none"></span>
</div> </div>
@ -106,7 +106,7 @@
style="display: none" style="display: none"
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{% call icons::bag("h-4 w-4 text-accent shrink-0") %} {{ icons::bag("h-4 w-4 text-accent shrink-0") }}
<span class="font-medium text-text" data-text="$_roastName"></span> <span class="font-medium text-text" data-text="$_roastName"></span>
<span class="text-xs text-text-muted" data-show="$_roasterName" data-text="$_roasterName" style="display: none"></span> <span class="text-xs text-text-muted" data-show="$_roasterName" data-text="$_roasterName" style="display: none"></span>
</div> </div>
@ -129,7 +129,7 @@
<div class="mt-4" data-show="$_step === 1" style="display: none"> <div class="mt-4" data-show="$_step === 1" style="display: none">
<div class="rounded-lg border bg-surface p-5"> <div class="rounded-lg border bg-surface p-5">
<div data-show="!$_reviewingCafe"> <div data-show="!$_reviewingCafe">
{% call location::location_search("$_error") %} {{ location::location_search("$_error") }}
</div> </div>
<!-- Review form for new cafe from Foursquare --> <!-- Review form for new cafe from Foursquare -->
@ -208,7 +208,7 @@
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/scan', {contentType: 'form'})" data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/scan', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_roastName = $_scanSuccess; $_step = 3 } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Scan failed. Please try again.' }" data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_roastName = $_scanSuccess; $_step = 3 } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Scan failed. Please try again.' }"
> >
{% call scan::scan_input("checkin-scan-form", "checkin-image", "$_extracting", "$_extractError", "Describe the coffee…", "Scanning…") %} {{ scan::scan_input("checkin-scan-form", "checkin-image", "$_extracting", "$_extractError", "Describe the coffee…", "Scanning…") }}
</form> </form>
</div> </div>

View file

@ -5,7 +5,7 @@
<div class="rounded-lg border bg-surface p-6"> <div class="rounded-lg border bg-surface p-6">
{% if token.is_some() %} {% if token.is_some() %}
<div class="flex flex-col items-center text-center gap-3"> <div class="flex flex-col items-center text-center gap-3">
{% call icons::check_circle("h-10 w-10 text-emerald-600 dark:text-emerald-400") %} {{ icons::check_circle("h-10 w-10 text-emerald-600 dark:text-emerald-400") }}
<h1 class="text-2xl font-semibold text-accent">CLI Authenticated</h1> <h1 class="text-2xl font-semibold text-accent">CLI Authenticated</h1>
<p class="text-sm text-text-secondary"> <p class="text-sm text-text-secondary">
Your CLI has been authenticated successfully. You can close this window and return to the terminal. Your CLI has been authenticated successfully. You can close this window and return to the terminal.
@ -13,7 +13,7 @@
</div> </div>
{% else if error.is_some() %} {% else if error.is_some() %}
<div class="flex flex-col items-center text-center gap-3"> <div class="flex flex-col items-center text-center gap-3">
{% call icons::x_circle("h-10 w-10 text-red-600 dark:text-red-400") %} {{ icons::x_circle("h-10 w-10 text-red-600 dark:text-red-400") }}
<h1 class="text-2xl font-semibold text-accent">Authentication Failed</h1> <h1 class="text-2xl font-semibold text-accent">Authentication Failed</h1>
<div class="w-full rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800 dark:bg-red-950/40 dark:border-red-800 dark:text-red-300"> <div class="w-full rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800 dark:bg-red-950/40 dark:border-red-800 dark:text-red-300">
{{ error.as_ref().unwrap() }} {{ error.as_ref().unwrap() }}
@ -21,7 +21,7 @@
</div> </div>
{% else %} {% else %}
<div class="flex flex-col items-center text-center gap-3"> <div class="flex flex-col items-center text-center gap-3">
{% call icons::spinner("h-10 w-10") %} {{ icons::spinner("h-10 w-10") }}
<h1 class="text-2xl font-semibold text-accent">CLI Authentication</h1> <h1 class="text-2xl font-semibold text-accent">CLI Authentication</h1>
<p class="text-sm text-text-secondary"> <p class="text-sm text-text-secondary">
Processing authentication&hellip; Processing authentication&hellip;

View file

@ -10,7 +10,7 @@
href="/add?type={{ active_type }}" href="/add?type={{ active_type }}"
class="inline-flex items-center gap-2 rounded-md bg-accent px-4 py-2.5 text-sm font-semibold text-accent-text transition hover:bg-accent-hover" class="inline-flex items-center gap-2 rounded-md bg-accent px-4 py-2.5 text-sm font-semibold text-accent-text transition hover:bg-accent-hover"
> >
{% call icons::plus_circle("h-4 w-4") %} {{ icons::plus_circle("h-4 w-4") }}
Add Add
</a> </a>
{% endif %} {% endif %}

View file

@ -57,7 +57,7 @@
data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/extract-bag-scan', {contentType: 'form'})" data-on:submit="$_extracting = true; $_extractError = ''; @post('/api/v1/extract-bag-scan', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_scanExtracted = true; document.getElementById('scan-extract-form').reset() } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Extraction failed. Please try again.' }" data-on:datastar-fetch="if (!$_extracting) return; if (evt.detail.type === 'finished') { $_extracting = false; $_scanExtracted = true; document.getElementById('scan-extract-form').reset() } else if (evt.detail.type === 'error') { $_extracting = false; $_extractError = 'Extraction failed. Please try again.' }"
> >
{% call scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError", "Describe the coffee bag…", "Waiting for response…") %} {{ scan::scan_input("scan-extract-form", "scan-image", "$_extracting", "$_extractError", "Describe the coffee bag…", "Waiting for response…") }}
</form> </form>
</div> </div>
@ -128,7 +128,7 @@
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-accent transition hover:text-accent-hover hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-accent transition hover:text-accent-hover hover:bg-surface-alt"
title="Brew Again" title="Brew Again"
> >
{% call icons::refresh("h-4 w-4") %} {{ icons::refresh("h-4 w-4") }}
</button> </button>
</form> </form>
{% endif %} {% endif %}
@ -148,7 +148,7 @@
</div> </div>
<div id="open-bags-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div id="open-bags-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for bag in open_bags %} {% for bag in open_bags %}
{% call bag_card::card(bag, is_authenticated) %} {{ bag_card::card(bag, is_authenticated) }}
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
@ -182,37 +182,37 @@
</div> </div>
<div class="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-7"> <div class="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-7">
<a href="/data?type=brews" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"> <a href="/data?type=brews" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
{% call icons::beaker("h-6 w-6 text-accent") %} {{ icons::beaker("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.brews }}</span> <span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.brews }}</span>
<span class="text-xs text-text-muted">Brews</span> <span class="text-xs text-text-muted">Brews</span>
</a> </a>
<a href="/data?type=roasts" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"> <a href="/data?type=roasts" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
{% call icons::fire("h-6 w-6 text-accent") %} {{ icons::fire("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasts }}</span> <span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasts }}</span>
<span class="text-xs text-text-muted">Roasts</span> <span class="text-xs text-text-muted">Roasts</span>
</a> </a>
<a href="/data?type=roasters" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"> <a href="/data?type=roasters" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
{% call icons::building("h-6 w-6 text-accent") %} {{ icons::building("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasters }}</span> <span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.roasters }}</span>
<span class="text-xs text-text-muted">Roasters</span> <span class="text-xs text-text-muted">Roasters</span>
</a> </a>
<a href="/data?type=bags" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"> <a href="/data?type=bags" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
{% call icons::bag("h-6 w-6 text-accent") %} {{ icons::bag("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.bags }}</span> <span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.bags }}</span>
<span class="text-xs text-text-muted">Bags</span> <span class="text-xs text-text-muted">Bags</span>
</a> </a>
<a href="/data?type=cups" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"> <a href="/data?type=cups" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
{% call icons::cup("h-6 w-6 text-accent") %} {{ icons::cup("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cups }}</span> <span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cups }}</span>
<span class="text-xs text-text-muted">Cups</span> <span class="text-xs text-text-muted">Cups</span>
</a> </a>
<a href="/data?type=cafes" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"> <a href="/data?type=cafes" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
{% call icons::location("h-6 w-6 text-accent") %} {{ icons::location("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cafes }}</span> <span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.cafes }}</span>
<span class="text-xs text-text-muted">Cafes</span> <span class="text-xs text-text-muted">Cafes</span>
</a> </a>
<a href="/data?type=gear" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt"> <a href="/data?type=gear" class="group flex flex-col items-center gap-1 rounded-lg border bg-surface p-4 transition hover:bg-surface-alt">
{% call icons::wrench("h-6 w-6 text-accent") %} {{ icons::wrench("h-6 w-6 text-accent") }}
<span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.gear }}</span> <span class="text-lg font-bold text-text group-hover:text-accent">{{ stats.gear }}</span>
<span class="text-xs text-text-muted">Gear</span> <span class="text-xs text-text-muted">Gear</span>
</a> </a>

View file

@ -12,7 +12,7 @@
{% if is_authenticated %} {% if is_authenticated %}
<div class="mt-3 pt-3 border-t flex gap-3"> <div class="mt-3 pt-3 border-t flex gap-3">
<a href="/add?type=brew&bag_id={{ bag.id }}" class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"> <a href="/add?type=brew&bag_id={{ bag.id }}" class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover">
{% call icons::plus_circle("h-4 w-4") %} {{ icons::plus_circle("h-4 w-4") }}
Brew Brew
</a> </a>
<button <button
@ -20,7 +20,7 @@
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-text" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-text"
onclick="closeBag('{{ bag.id }}', document.getElementById('bag-card-{{ bag.id }}'))" onclick="closeBag('{{ bag.id }}', document.getElementById('bag-card-{{ bag.id }}'))"
> >
{% call icons::x_circle("h-4 w-4") %} {{ icons::x_circle("h-4 w-4") }}
Close Close
</button> </button>
</div> </div>

View file

@ -16,7 +16,7 @@
<h3 class="text-base font-semibold text-text">Roaster</h3> <h3 class="text-base font-semibold text-text">Roaster</h3>
<div class="mt-2 rounded-lg border bg-surface px-4 py-3 flex items-center justify-between"> <div class="mt-2 rounded-lg border bg-surface px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{% call icons::fire("h-4 w-4 text-accent shrink-0") %} {{ icons::fire("h-4 w-4 text-accent shrink-0") }}
<span class="font-medium text-text" data-text="$_roasterName"></span> <span class="font-medium text-text" data-text="$_roasterName"></span>
<span class="text-xs text-text-muted" data-show="$_roasterCountry" data-text="$_roasterCountry" style="display:none"></span> <span class="text-xs text-text-muted" data-show="$_roasterCountry" data-text="$_roasterCountry" style="display:none"></span>
</div> </div>
@ -53,7 +53,7 @@
<h3 class="text-base font-semibold text-text">Roast</h3> <h3 class="text-base font-semibold text-text">Roast</h3>
<div class="mt-2 rounded-lg border bg-surface px-4 py-3 flex items-center justify-between"> <div class="mt-2 rounded-lg border bg-surface px-4 py-3 flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{% call icons::bag("h-4 w-4 text-accent shrink-0") %} {{ icons::bag("h-4 w-4 text-accent shrink-0") }}
<span class="font-medium text-text" data-text="$_roastName"></span> <span class="font-medium text-text" data-text="$_roastName"></span>
<span class="text-xs text-text-muted" data-show="$_origin" data-text="$_origin" style="display:none"></span> <span class="text-xs text-text-muted" data-show="$_origin" data-text="$_origin" style="display:none"></span>
</div> </div>
@ -107,7 +107,7 @@
</div> </div>
<p data-show="$_scanError" data-text="$_scanError" style="display:none" class="text-sm text-red-600"></p> <p data-show="$_scanError" data-text="$_scanError" style="display:none" class="text-sm text-red-600"></p>
<div data-show="$_scanSubmitting" style="display:none" class="flex items-center gap-3 text-sm text-accent"> <div data-show="$_scanSubmitting" style="display:none" class="flex items-center gap-3 text-sm text-accent">
{% call icons::spinner("h-5 w-5") %} {{ icons::spinner("h-5 w-5") }}
Saving&hellip; Saving&hellip;
</div> </div>
<div data-show="!$_scanSubmitting" class="flex items-center justify-end gap-2"> <div data-show="!$_scanSubmitting" class="flex items-center justify-end gap-2">

View file

@ -18,16 +18,16 @@
<section class="rounded-lg border bg-surface" <section class="rounded-lg border bg-surface"
{% if bags.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(bags.next_page().unwrap())|safe }}" data-target="#bag-list"{% endif %} {% if bags.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(bags.next_page().unwrap())|safe }}" data-target="#bag-list"{% endif %}
> >
{% call table::search_header(navigator, "#bag-list") %} {{ table::search_header(navigator, "#bag-list") }}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="responsive-table min-w-full divide-y text-left text-sm text-text"> <table class="responsive-table min-w-full divide-y text-left text-sm text-text">
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text"> <thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#bag-list") %} {{ table::sortable_header("Added", "created-at", navigator, "#bag-list") }}
{% call table::sortable_header("Roaster", "roaster", navigator, "#bag-list") %} {{ table::sortable_header("Roaster", "roaster", navigator, "#bag-list") }}
{% call table::sortable_header("Roast", "roast", navigator, "#bag-list") %} {{ table::sortable_header("Roast", "roast", navigator, "#bag-list") }}
{% call table::sortable_header("Status", "status", navigator, "#bag-list") %} {{ table::sortable_header("Status", "status", navigator, "#bag-list") }}
{% call table::sortable_header("Finished", "finished-at", navigator, "#bag-list") %} {{ table::sortable_header("Finished", "finished-at", navigator, "#bag-list") }}
{% if is_authenticated %} {% if is_authenticated %}
<th scope="col" class="actions-col px-4 py-3 text-right">Actions</th> <th scope="col" class="actions-col px-4 py-3 text-right">Actions</th>
{% endif %} {% endif %}
@ -83,13 +83,13 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
data-on:click="confirm('Close this bag? This will mark it as finished.') && @put('/api/v1/bags/{{ bag.id }}?closed=true&{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})"> data-on:click="confirm('Close this bag? This will mark it as finished.') && @put('/api/v1/bags/{{ bag.id }}?closed=true&{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})">
{% call icons::x_circle("h-4 w-4") %} Close bag {{ icons::x_circle("h-4 w-4") }} Close bag
</button> </button>
{% endif %} {% endif %}
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this bag?') && @delete('/api/v1/bags/{{ bag.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})"> data-on:click="confirm('Delete this bag?') && @delete('/api/v1/bags/{{ bag.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -103,8 +103,8 @@
<button type="button" <button type="button"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
title="Actions"> title="Actions">
<span class="icon-expand">{% call icons::ellipsis_vertical("h-5 w-5") %}</span> <span class="icon-expand">{{ icons::ellipsis_vertical("h-5 w-5") }}</span>
<span class="icon-collapse hidden">{% call icons::chevron_up("h-5 w-5") %}</span> <span class="icon-collapse hidden">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
</div> </div>
</td> </td>
@ -118,13 +118,13 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
data-on:click="confirm('Close this bag? This will mark it as finished.') && @put('/api/v1/bags/{{ bag.id }}?closed=true&{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})"> data-on:click="confirm('Close this bag? This will mark it as finished.') && @put('/api/v1/bags/{{ bag.id }}?closed=true&{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})">
{% call icons::x_circle("h-4 w-4") %} Close bag {{ icons::x_circle("h-4 w-4") }} Close bag
</button> </button>
{% endif %} {% endif %}
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this bag?') && @delete('/api/v1/bags/{{ bag.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})"> data-on:click="confirm('Delete this bag?') && @delete('/api/v1/bags/{{ bag.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -137,7 +137,7 @@
{% if bags.items.is_empty() %} {% if bags.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No bags match your search.</div> <div class="p-8 text-center text-text-muted">No bags match your search.</div>
{% endif %} {% endif %}
{% call table::pagination_header(bags, navigator, "#bag-list") %} {{ table::pagination_header(bags, navigator, "#bag-list") }}
{% if bags.has_next() %} {% if bags.has_next() %}
<div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div> <div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div>
{% endif %} {% endif %}

View file

@ -17,13 +17,13 @@
<input type="hidden" name="quick_notes" value="{{ brew.quick_notes_raw }}" /> <input type="hidden" name="quick_notes" value="{{ brew.quick_notes_raw }}" />
<button type="submit" <button type="submit"
class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"> class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover">
{% call icons::plus_circle("h-4 w-4") %} Brew Again {{ icons::plus_circle("h-4 w-4") }} Brew Again
</button> </button>
</form> </form>
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this brew?') && @delete('/api/v1/brews/{{ brew.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#brew-list', mode: 'replace'}})"> data-on:click="confirm('Delete this brew?') && @delete('/api/v1/brews/{{ brew.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#brew-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
{% endmacro %} {% endmacro %}
@ -45,13 +45,13 @@
<section class="rounded-lg border bg-surface" <section class="rounded-lg border bg-surface"
{% if brews.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(brews.next_page().unwrap())|safe }}" data-target="#brew-list"{% endif %} {% if brews.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(brews.next_page().unwrap())|safe }}" data-target="#brew-list"{% endif %}
> >
{% call table::search_header(navigator, "#brew-list") %} {{ table::search_header(navigator, "#brew-list") }}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="responsive-table min-w-full divide-y text-left text-sm text-text"> <table class="responsive-table min-w-full divide-y text-left text-sm text-text">
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text"> <thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#brew-list") %} {{ table::sortable_header("Added", "created-at", navigator, "#brew-list") }}
<th scope="col" class="px-4 py-3">Coffee</th> <th scope="col" class="px-4 py-3">Coffee</th>
<th scope="col" class="px-4 py-3">Grind</th> <th scope="col" class="px-4 py-3">Grind</th>
<th scope="col" class="px-4 py-3">Recipe</th> <th scope="col" class="px-4 py-3">Recipe</th>
@ -109,14 +109,14 @@
</td> </td>
{% if is_authenticated %} {% if is_authenticated %}
<td data-label="" class="card-detail hidden"> <td data-label="" class="card-detail hidden">
{% call brew_actions(brew, navigator) %} {{ brew_actions(brew, navigator) }}
</td> </td>
<td data-label="" class="card-actions px-4 py-3 text-right"> <td data-label="" class="card-actions px-4 py-3 text-right">
<button type="button" <button type="button"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
title="Actions"> title="Actions">
<span class="icon-expand">{% call icons::ellipsis_vertical("h-5 w-5") %}</span> <span class="icon-expand">{{ icons::ellipsis_vertical("h-5 w-5") }}</span>
<span class="icon-collapse hidden">{% call icons::chevron_up("h-5 w-5") %}</span> <span class="icon-collapse hidden">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
</td> </td>
{% endif %} {% endif %}
@ -124,7 +124,7 @@
{% if is_authenticated %} {% if is_authenticated %}
<tr class="hidden detail-row"> <tr class="hidden detail-row">
<td colspan="7" class="bg-surface-alt px-4 py-2"> <td colspan="7" class="bg-surface-alt px-4 py-2">
{% call brew_actions(brew, navigator) %} {{ brew_actions(brew, navigator) }}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -139,7 +139,7 @@
</div> </div>
{% endif %} {% endif %}
{% call table::pagination_header(brews, navigator, "#brew-list") %} {{ table::pagination_header(brews, navigator, "#brew-list") }}
{% if brews.has_next() %} {% if brews.has_next() %}
<div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div> <div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div>

View file

@ -18,15 +18,15 @@
<section class="rounded-lg border bg-surface" <section class="rounded-lg border bg-surface"
{% if cafes.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(cafes.next_page().unwrap())|safe }}" data-target="#cafe-list"{% endif %} {% if cafes.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(cafes.next_page().unwrap())|safe }}" data-target="#cafe-list"{% endif %}
> >
{% call table::search_header(navigator, "#cafe-list") %} {{ table::search_header(navigator, "#cafe-list") }}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="responsive-table min-w-full divide-y text-left text-sm text-text"> <table class="responsive-table min-w-full divide-y text-left text-sm text-text">
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text"> <thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#cafe-list") %} {{ table::sortable_header("Added", "created-at", navigator, "#cafe-list") }}
{% call table::sortable_header("Name", "name", navigator, "#cafe-list") %} {{ table::sortable_header("Name", "name", navigator, "#cafe-list") }}
{% call table::sortable_header("City", "city", navigator, "#cafe-list") %} {{ table::sortable_header("City", "city", navigator, "#cafe-list") }}
{% call table::sortable_header("Country", "country", navigator, "#cafe-list") %} {{ table::sortable_header("Country", "country", navigator, "#cafe-list") }}
<th scope="col" class="actions-col px-4 py-3 text-right">Actions</th> <th scope="col" class="actions-col px-4 py-3 text-right">Actions</th>
</tr> </tr>
</thead> </thead>
@ -56,19 +56,19 @@
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" <a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
href="{{ cafe.map_url }}" target="_blank" rel="noreferrer noopener"> href="{{ cafe.map_url }}" target="_blank" rel="noreferrer noopener">
{% call icons::location("h-4 w-4") %} View Map {{ icons::location("h-4 w-4") }} View Map
</a> </a>
{% if cafe.has_website %} {% if cafe.has_website %}
<a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" <a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
href="{{ cafe.website_url }}" target="_blank" rel="noreferrer noopener"> href="{{ cafe.website_url }}" target="_blank" rel="noreferrer noopener">
{% call icons::external_link("h-4 w-4") %} Homepage {{ icons::external_link("h-4 w-4") }} Homepage
</a> </a>
{% endif %} {% endif %}
{% if is_authenticated %} {% if is_authenticated %}
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this cafe?') && @delete('/api/v1/cafes/{{ cafe.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cafe-list', mode: 'replace'}})"> data-on:click="confirm('Delete this cafe?') && @delete('/api/v1/cafes/{{ cafe.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cafe-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -77,8 +77,8 @@
<button type="button" <button type="button"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
title="Actions"> title="Actions">
<span class="icon-expand">{% call icons::ellipsis_vertical("h-5 w-5") %}</span> <span class="icon-expand">{{ icons::ellipsis_vertical("h-5 w-5") }}</span>
<span class="icon-collapse hidden">{% call icons::chevron_up("h-5 w-5") %}</span> <span class="icon-collapse hidden">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
</td> </td>
</tr> </tr>
@ -87,19 +87,19 @@
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" <a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
href="{{ cafe.map_url }}" target="_blank" rel="noreferrer noopener"> href="{{ cafe.map_url }}" target="_blank" rel="noreferrer noopener">
{% call icons::location("h-4 w-4") %} View Map {{ icons::location("h-4 w-4") }} View Map
</a> </a>
{% if cafe.has_website %} {% if cafe.has_website %}
<a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" <a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
href="{{ cafe.website_url }}" target="_blank" rel="noreferrer noopener"> href="{{ cafe.website_url }}" target="_blank" rel="noreferrer noopener">
{% call icons::external_link("h-4 w-4") %} Homepage {{ icons::external_link("h-4 w-4") }} Homepage
</a> </a>
{% endif %} {% endif %}
{% if is_authenticated %} {% if is_authenticated %}
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this cafe?') && @delete('/api/v1/cafes/{{ cafe.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cafe-list', mode: 'replace'}})"> data-on:click="confirm('Delete this cafe?') && @delete('/api/v1/cafes/{{ cafe.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cafe-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -112,7 +112,7 @@
{% if cafes.items.is_empty() %} {% if cafes.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No cafes match your search.</div> <div class="p-8 text-center text-text-muted">No cafes match your search.</div>
{% endif %} {% endif %}
{% call table::pagination_header(cafes, navigator, "#cafe-list") %} {{ table::pagination_header(cafes, navigator, "#cafe-list") }}
{% if cafes.has_next() %} {% if cafes.has_next() %}
<div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div> <div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div>
{% endif %} {% endif %}

View file

@ -18,16 +18,16 @@
<section class="rounded-lg border bg-surface" <section class="rounded-lg border bg-surface"
{% if cups.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(cups.next_page().unwrap())|safe }}" data-target="#cup-list"{% endif %} {% if cups.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(cups.next_page().unwrap())|safe }}" data-target="#cup-list"{% endif %}
> >
{% call table::search_header(navigator, "#cup-list") %} {{ table::search_header(navigator, "#cup-list") }}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="responsive-table min-w-full divide-y text-left text-sm text-text"> <table class="responsive-table min-w-full divide-y text-left text-sm text-text">
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text"> <thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#cup-list") %} {{ table::sortable_header("Added", "created-at", navigator, "#cup-list") }}
{% call table::sortable_header("Roast", "roast", navigator, "#cup-list") %} {{ table::sortable_header("Roast", "roast", navigator, "#cup-list") }}
{% call table::sortable_header("Roaster", "roaster", navigator, "#cup-list") %} {{ table::sortable_header("Roaster", "roaster", navigator, "#cup-list") }}
{% call table::sortable_header("Cafe", "cafe", navigator, "#cup-list") %} {{ table::sortable_header("Cafe", "cafe", navigator, "#cup-list") }}
{% call table::sortable_header("City", "city", navigator, "#cup-list") %} {{ table::sortable_header("City", "city", navigator, "#cup-list") }}
{% if is_authenticated %} {% if is_authenticated %}
<th scope="col" class="actions-col px-4 py-3 text-right">Actions</th> <th scope="col" class="actions-col px-4 py-3 text-right">Actions</th>
{% endif %} {% endif %}
@ -61,7 +61,7 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this cup?') && @delete('/api/v1/cups/{{ cup.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cup-list', mode: 'replace'}})"> data-on:click="confirm('Delete this cup?') && @delete('/api/v1/cups/{{ cup.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cup-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -69,8 +69,8 @@
<button type="button" <button type="button"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
title="Actions"> title="Actions">
<span class="icon-expand">{% call icons::ellipsis_vertical("h-5 w-5") %}</span> <span class="icon-expand">{{ icons::ellipsis_vertical("h-5 w-5") }}</span>
<span class="icon-collapse hidden">{% call icons::chevron_up("h-5 w-5") %}</span> <span class="icon-collapse hidden">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
</td> </td>
{% endif %} {% endif %}
@ -82,7 +82,7 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this cup?') && @delete('/api/v1/cups/{{ cup.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cup-list', mode: 'replace'}})"> data-on:click="confirm('Delete this cup?') && @delete('/api/v1/cups/{{ cup.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#cup-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -95,7 +95,7 @@
{% if cups.items.is_empty() %} {% if cups.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No cups match your search.</div> <div class="p-8 text-center text-text-muted">No cups match your search.</div>
{% endif %} {% endif %}
{% call table::pagination_header(cups, navigator, "#cup-list") %} {{ table::pagination_header(cups, navigator, "#cup-list") }}
{% if cups.has_next() %} {% if cups.has_next() %}
<div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div> <div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div>
{% endif %} {% endif %}

View file

@ -18,16 +18,16 @@
<section class="rounded-lg border bg-surface" <section class="rounded-lg border bg-surface"
{% if gear.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(gear.next_page().unwrap())|safe }}" data-target="#gear-list"{% endif %} {% if gear.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(gear.next_page().unwrap())|safe }}" data-target="#gear-list"{% endif %}
> >
{% call table::search_header(navigator, "#gear-list") %} {{ table::search_header(navigator, "#gear-list") }}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="responsive-table min-w-full divide-y text-left text-sm text-text"> <table class="responsive-table min-w-full divide-y text-left text-sm text-text">
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text"> <thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#gear-list") %} {{ table::sortable_header("Added", "created-at", navigator, "#gear-list") }}
{% call table::sortable_header("Category", "category", navigator, "#gear-list") %} {{ table::sortable_header("Category", "category", navigator, "#gear-list") }}
{% call table::sortable_header("Make", "make", navigator, "#gear-list") %} {{ table::sortable_header("Make", "make", navigator, "#gear-list") }}
{% call table::sortable_header("Model", "model", navigator, "#gear-list") %} {{ table::sortable_header("Model", "model", navigator, "#gear-list") }}
{% if is_authenticated %} {% if is_authenticated %}
<th scope="col" class="actions-col px-4 py-3 text-right">Actions</th> <th scope="col" class="actions-col px-4 py-3 text-right">Actions</th>
{% endif %} {% endif %}
@ -58,7 +58,7 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this gear?') && @delete('/api/v1/gear/{{ item.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#gear-list', mode: 'replace'}})"> data-on:click="confirm('Delete this gear?') && @delete('/api/v1/gear/{{ item.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#gear-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -66,8 +66,8 @@
<button type="button" <button type="button"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
title="Actions"> title="Actions">
<span class="icon-expand">{% call icons::ellipsis_vertical("h-5 w-5") %}</span> <span class="icon-expand">{{ icons::ellipsis_vertical("h-5 w-5") }}</span>
<span class="icon-collapse hidden">{% call icons::chevron_up("h-5 w-5") %}</span> <span class="icon-collapse hidden">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
</td> </td>
{% endif %} {% endif %}
@ -79,7 +79,7 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this gear?') && @delete('/api/v1/gear/{{ item.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#gear-list', mode: 'replace'}})"> data-on:click="confirm('Delete this gear?') && @delete('/api/v1/gear/{{ item.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#gear-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -92,7 +92,7 @@
{% if gear.items.is_empty() %} {% if gear.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No gear matches your search.</div> <div class="p-8 text-center text-text-muted">No gear matches your search.</div>
{% endif %} {% endif %}
{% call table::pagination_header(gear, navigator, "#gear-list") %} {{ table::pagination_header(gear, navigator, "#gear-list") }}
{% if gear.has_next() %} {% if gear.has_next() %}
<div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div> <div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div>
{% endif %} {% endif %}

View file

@ -18,14 +18,14 @@
<section class="rounded-lg border bg-surface" <section class="rounded-lg border bg-surface"
{% if roasts.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(roasts.next_page().unwrap())|safe }}" data-target="#roast-list"{% endif %} {% if roasts.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(roasts.next_page().unwrap())|safe }}" data-target="#roast-list"{% endif %}
> >
{% call table::search_header(navigator, "#roast-list") %} {{ table::search_header(navigator, "#roast-list") }}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="responsive-table min-w-full divide-y text-left text-sm text-text"> <table class="responsive-table min-w-full divide-y text-left text-sm text-text">
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text"> <thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#roast-list") %} {{ table::sortable_header("Added", "created-at", navigator, "#roast-list") }}
{% call table::sortable_header("Roast", "name", navigator, "#roast-list") %} {{ table::sortable_header("Roast", "name", navigator, "#roast-list") }}
{% call table::sortable_header("Origin", "origin", navigator, "#roast-list") %} {{ table::sortable_header("Origin", "origin", navigator, "#roast-list") }}
<th scope="col" class="px-4 py-3">Tasting Notes</th> <th scope="col" class="px-4 py-3">Tasting Notes</th>
{% if is_authenticated %} {% if is_authenticated %}
<th scope="col" class="actions-col px-4 py-3 text-right">Actions</th> <th scope="col" class="actions-col px-4 py-3 text-right">Actions</th>
@ -79,7 +79,7 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this roast?') && @delete('/api/v1/roasts/{{ roast.full_id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roast-list', mode: 'replace'}})"> data-on:click="confirm('Delete this roast?') && @delete('/api/v1/roasts/{{ roast.full_id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roast-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -87,8 +87,8 @@
<button type="button" <button type="button"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
title="Actions"> title="Actions">
<span class="icon-expand">{% call icons::ellipsis_vertical("h-5 w-5") %}</span> <span class="icon-expand">{{ icons::ellipsis_vertical("h-5 w-5") }}</span>
<span class="icon-collapse hidden">{% call icons::chevron_up("h-5 w-5") %}</span> <span class="icon-collapse hidden">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
</td> </td>
{% endif %} {% endif %}
@ -100,7 +100,7 @@
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this roast?') && @delete('/api/v1/roasts/{{ roast.full_id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roast-list', mode: 'replace'}})"> data-on:click="confirm('Delete this roast?') && @delete('/api/v1/roasts/{{ roast.full_id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roast-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
</div> </div>
</td> </td>
@ -113,7 +113,7 @@
{% if roasts.items.is_empty() %} {% if roasts.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No roasts match your search.</div> <div class="p-8 text-center text-text-muted">No roasts match your search.</div>
{% endif %} {% endif %}
{% call table::pagination_header(roasts, navigator, "#roast-list") %} {{ table::pagination_header(roasts, navigator, "#roast-list") }}
{% if roasts.has_next() %} {% if roasts.has_next() %}
<div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div> <div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div>
{% endif %} {% endif %}

View file

@ -18,15 +18,15 @@
<section class="rounded-lg border bg-surface" <section class="rounded-lg border bg-surface"
{% if roasters.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(roasters.next_page().unwrap())|safe }}" data-target="#roaster-list"{% endif %} {% if roasters.has_next() %}data-infinite-scroll data-next-url="{{ navigator.fragment_page_href(roasters.next_page().unwrap())|safe }}" data-target="#roaster-list"{% endif %}
> >
{% call table::search_header(navigator, "#roaster-list") %} {{ table::search_header(navigator, "#roaster-list") }}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="responsive-table min-w-full divide-y text-left text-sm text-text"> <table class="responsive-table min-w-full divide-y text-left text-sm text-text">
<thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text"> <thead class="bg-surface-alt text-xs font-semibold tracking-wide text-text">
<tr> <tr>
{% call table::sortable_header("Added", "created-at", navigator, "#roaster-list") %} {{ table::sortable_header("Added", "created-at", navigator, "#roaster-list") }}
{% call table::sortable_header("Name", "name", navigator, "#roaster-list") %} {{ table::sortable_header("Name", "name", navigator, "#roaster-list") }}
{% call table::sortable_header("Country", "country", navigator, "#roaster-list") %} {{ table::sortable_header("Country", "country", navigator, "#roaster-list") }}
{% call table::sortable_header("City", "city", navigator, "#roaster-list") %} {{ table::sortable_header("City", "city", navigator, "#roaster-list") }}
<th scope="col" class="actions-col px-4 py-3 text-right">Actions</th> <th scope="col" class="actions-col px-4 py-3 text-right">Actions</th>
</tr> </tr>
</thead> </thead>
@ -60,14 +60,14 @@
{% if roaster.has_homepage %} {% if roaster.has_homepage %}
<a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" <a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
href="{{ roaster.homepage_url }}" target="_blank" rel="noreferrer noopener"> href="{{ roaster.homepage_url }}" target="_blank" rel="noreferrer noopener">
{% call icons::external_link("h-4 w-4") %} Homepage {{ icons::external_link("h-4 w-4") }} Homepage
</a> </a>
{% endif %} {% endif %}
{% if is_authenticated %} {% if is_authenticated %}
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this roaster?') && @delete('/api/v1/roasters/{{ roaster.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roaster-list', mode: 'replace'}})"> data-on:click="confirm('Delete this roaster?') && @delete('/api/v1/roasters/{{ roaster.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roaster-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -78,8 +78,8 @@
<button type="button" <button type="button"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt" class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
title="Actions"> title="Actions">
<span class="icon-expand">{% call icons::ellipsis_vertical("h-5 w-5") %}</span> <span class="icon-expand">{{ icons::ellipsis_vertical("h-5 w-5") }}</span>
<span class="icon-collapse hidden">{% call icons::chevron_up("h-5 w-5") %}</span> <span class="icon-collapse hidden">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
{% endif %} {% endif %}
</td> </td>
@ -91,14 +91,14 @@
{% if roaster.has_homepage %} {% if roaster.has_homepage %}
<a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover" <a class="inline-flex items-center gap-1 text-sm font-medium text-accent hover:text-accent-hover"
href="{{ roaster.homepage_url }}" target="_blank" rel="noreferrer noopener"> href="{{ roaster.homepage_url }}" target="_blank" rel="noreferrer noopener">
{% call icons::external_link("h-4 w-4") %} Homepage {{ icons::external_link("h-4 w-4") }} Homepage
</a> </a>
{% endif %} {% endif %}
{% if is_authenticated %} {% if is_authenticated %}
<button type="button" <button type="button"
class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600" class="inline-flex items-center gap-1 text-sm font-medium text-text-muted hover:text-red-600"
data-on:click="confirm('Delete this roaster?') && @delete('/api/v1/roasters/{{ roaster.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roaster-list', mode: 'replace'}})"> data-on:click="confirm('Delete this roaster?') && @delete('/api/v1/roasters/{{ roaster.id }}?{{ navigator.query() }}', {responseOverrides: {selector: '#roaster-list', mode: 'replace'}})">
{% call icons::delete("h-4 w-4") %} Delete {{ icons::delete("h-4 w-4") }} Delete
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -112,7 +112,7 @@
{% if roasters.items.is_empty() %} {% if roasters.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No roasters match your search.</div> <div class="p-8 text-center text-text-muted">No roasters match your search.</div>
{% endif %} {% endif %}
{% call table::pagination_header(roasters, navigator, "#roaster-list") %} {{ table::pagination_header(roasters, navigator, "#roaster-list") }}
{% if roasters.has_next() %} {% if roasters.has_next() %}
<div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div> <div class="infinite-scroll-sentinel h-4 md:hidden" aria-hidden="true"></div>
{% endif %} {% endif %}

View file

@ -43,8 +43,8 @@ const locateUser = (triggerEl) => {
data-attr:disabled="$_locating || $_locationFound" data-attr:disabled="$_locating || $_locationFound"
aria-label="Use my location" aria-label="Use my location"
> >
<span data-show="!$_locating">{% call icons::location("h-5 w-5") %}</span> <span data-show="!$_locating">{{ icons::location("h-5 w-5") }}</span>
<span data-show="$_locating" style="display: none">{% call icons::spinner("h-5 w-5") %}</span> <span data-show="$_locating" style="display: none">{{ icons::spinner("h-5 w-5") }}</span>
</button> </button>
<input type="text" data-bind:_city-name <input type="text" data-bind:_city-name
class="input-field w-full text-sm" class="input-field w-full text-sm"

View file

@ -4,41 +4,41 @@
<div class="font-semibold uppercase tracking-[0.25em] text-accent"><a href="/">B{rew}log</a></div> <div class="font-semibold uppercase tracking-[0.25em] text-accent"><a href="/">B{rew}log</a></div>
<div class="relative flex items-center gap-1"> <div class="relative flex items-center gap-1">
<a class="rounded-md p-1.5 transition {% if nav_active == "home" %}text-accent{% else %}text-text-muted hover:text-text-secondary{% endif %}" href="/" title="Home" aria-label="Home"> <a class="rounded-md p-1.5 transition {% if nav_active == "home" %}text-accent{% else %}text-text-muted hover:text-text-secondary{% endif %}" href="/" title="Home" aria-label="Home">
{% call icons::home("h-5 w-5") %} {{ icons::home("h-5 w-5") }}
</a> </a>
<button type="button" class="rounded-md p-1.5 text-text-muted transition hover:text-text-secondary" title="Toggle theme" aria-label="Toggle theme" onclick="toggleTheme()"> <button type="button" class="rounded-md p-1.5 text-text-muted transition hover:text-text-secondary" title="Toggle theme" aria-label="Toggle theme" onclick="toggleTheme()">
<span class="theme-icon-light">{% call icons::sun("h-5 w-5") %}</span> <span class="theme-icon-light">{{ icons::sun("h-5 w-5") }}</span>
<span class="theme-icon-dark hidden">{% call icons::moon("h-5 w-5") %}</span> <span class="theme-icon-dark hidden">{{ icons::moon("h-5 w-5") }}</span>
</button> </button>
<button class="rounded-md p-1.5 text-text-muted transition hover:text-text-secondary" data-on:click="$_navOpen = !$_navOpen" title="Menu" aria-label="Toggle menu"> <button class="rounded-md p-1.5 text-text-muted transition hover:text-text-secondary" data-on:click="$_navOpen = !$_navOpen" title="Menu" aria-label="Toggle menu">
<span data-show="!$_navOpen">{% call icons::hamburger("h-5 w-5") %}</span> <span data-show="!$_navOpen">{{ icons::hamburger("h-5 w-5") }}</span>
<span data-show="$_navOpen" style="display:none">{% call icons::close("h-5 w-5") %}</span> <span data-show="$_navOpen" style="display:none">{{ icons::close("h-5 w-5") }}</span>
</button> </button>
<!-- Backdrop (click outside to close) --> <!-- Backdrop (click outside to close) -->
<div class="fixed inset-0 z-40" data-show="$_navOpen" data-on:click="$_navOpen = false" style="display:none"></div> <div class="fixed inset-0 z-40" data-show="$_navOpen" data-on:click="$_navOpen = false" style="display:none"></div>
<!-- Dropdown menu --> <!-- Dropdown menu -->
<div class="absolute right-0 top-full z-50 mt-2 flex flex-col gap-1 rounded-lg border bg-surface p-3 whitespace-nowrap" data-show="$_navOpen" style="display:none"> <div class="absolute right-0 top-full z-50 mt-2 flex flex-col gap-1 rounded-lg border bg-surface p-3 whitespace-nowrap" data-show="$_navOpen" style="display:none">
<a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "data" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/data"> <a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "data" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/data">
Data {% call icons::squares("h-4 w-4 shrink-0") %} Data {{ icons::squares("h-4 w-4 shrink-0") }}
</a> </a>
<a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "timeline" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/timeline"> <a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "timeline" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/timeline">
Timeline {% call icons::timeline("h-4 w-4 shrink-0") %} Timeline {{ icons::timeline("h-4 w-4 shrink-0") }}
</a> </a>
{% if is_authenticated %} {% if is_authenticated %}
<a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "checkin" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/check-in"> <a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "checkin" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/check-in">
Check In {% call icons::checkin("h-4 w-4 shrink-0") %} Check In {{ icons::checkin("h-4 w-4 shrink-0") }}
</a> </a>
<a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "account" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/account"> <a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 {% if nav_active == "account" %}text-accent font-medium{% else %}text-text-muted hover:text-text hover:bg-surface-alt{% endif %}" href="/account">
Admin {% call icons::cog("h-4 w-4 shrink-0") %} Admin {{ icons::cog("h-4 w-4 shrink-0") }}
</a> </a>
<form method="post" action="/logout"> <form method="post" action="/logout">
<button type="submit" class="w-full rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 text-text-muted hover:text-text hover:bg-surface-alt"> <button type="submit" class="w-full rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 text-text-muted hover:text-text hover:bg-surface-alt">
Sign Out {% call icons::logout("h-4 w-4 shrink-0") %} Sign Out {{ icons::logout("h-4 w-4 shrink-0") }}
</button> </button>
</form> </form>
{% else %} {% else %}
<a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 text-text-muted hover:text-text hover:bg-surface-alt" href="/login"> <a class="rounded-md px-4 py-2.5 transition flex items-center justify-end gap-4 text-text-muted hover:text-text hover:bg-surface-alt" href="/login">
Login {% call icons::user("h-4 w-4 shrink-0") %} Login {{ icons::user("h-4 w-4 shrink-0") }}
</a> </a>
{% endif %} {% endif %}
</div> </div>

View file

@ -7,7 +7,7 @@
class="shrink-0 inline-flex items-center justify-center rounded-md border p-2.5 text-accent transition hover:bg-surface-alt cursor-pointer" class="shrink-0 inline-flex items-center justify-center rounded-md border p-2.5 text-accent transition hover:bg-surface-alt cursor-pointer"
aria-label="Take Photo" aria-label="Take Photo"
> >
{% call icons::camera("h-5 w-5") %} {{ icons::camera("h-5 w-5") }}
</brew-photo-capture> </brew-photo-capture>
<input <input
type="text" type="text"
@ -17,7 +17,7 @@
/> />
</div> </div>
<div data-show="{{ extracting_signal }}" style="display:none" class="flex items-center gap-3 text-sm text-accent"> <div data-show="{{ extracting_signal }}" style="display:none" class="flex items-center gap-3 text-sm text-accent">
{% call icons::spinner("h-5 w-5") %} {{ icons::spinner("h-5 w-5") }}
{{ spinner_text }} {{ spinner_text }}
</div> </div>
<p data-show="{{ error_signal }}" data-text="{{ error_signal }}" style="display:none" class="mt-2 text-sm text-red-600"></p> <p data-show="{{ error_signal }}" data-text="{{ error_signal }}" style="display:none" class="mt-2 text-sm text-red-600"></p>

View file

@ -26,8 +26,8 @@
<span data-show="{{ tab_signal_js }} === '{{ tab.key }}'">{{ tab.label }}</span> <span data-show="{{ tab_signal_js }} === '{{ tab.key }}'">{{ tab.label }}</span>
{% endfor %} {% endfor %}
</span> </span>
<span data-show="!$_tabsOpen">{% call icons::chevron_down("h-5 w-5") %}</span> <span data-show="!$_tabsOpen">{{ icons::chevron_down("h-5 w-5") }}</span>
<span data-show="$_tabsOpen" style="display:none">{% call icons::chevron_up("h-5 w-5") %}</span> <span data-show="$_tabsOpen" style="display:none">{{ icons::chevron_up("h-5 w-5") }}</span>
</button> </button>
<div class="flex flex-col border-t" data-show="$_tabsOpen" style="display:none"> <div class="flex flex-col border-t" data-show="$_tabsOpen" style="display:none">
{% for tab in tabs %} {% for tab in tabs %}

View file

@ -2,7 +2,7 @@
<h2 id="{{ month.anchor }}" class="timeline-heading scroll-mt-24 mb-6 text-2xl font-semibold text-text" data-timeline-month> <h2 id="{{ month.anchor }}" class="timeline-heading scroll-mt-24 mb-6 text-2xl font-semibold text-text" data-timeline-month>
{{ month.heading }} {{ month.heading }}
<button type="button" class="timeline-top-btn" onclick="window.scrollTo({top: 0, behavior: 'smooth'})" aria-label="Back to top"> <button type="button" class="timeline-top-btn" onclick="window.scrollTo({top: 0, behavior: 'smooth'})" aria-label="Back to top">
{% call icons::arrow_up("h-5 w-5") %} {{ icons::arrow_up("h-5 w-5") }}
</button> </button>
</h2> </h2>
{% for event in month.events %} {% for event in month.events %}
@ -33,7 +33,7 @@
rel="noreferrer noopener" rel="noreferrer noopener"
aria-label="Open external link" aria-label="Open external link"
> >
{% call icons::external_link("h-4 w-4") %} {{ icons::external_link("h-4 w-4") }}
<span class="sr-only">Open external link</span> <span class="sr-only">Open external link</span>
</a> </a>
{% endif %} {% endif %}
@ -59,7 +59,7 @@
title="Brew again" title="Brew again"
> >
<span class="sr-only">Brew again</span> <span class="sr-only">Brew again</span>
{% call icons::refresh("h-4 w-4") %} {{ icons::refresh("h-4 w-4") }}
</button> </button>
</form> </form>
{% endif %} {% endif %}
@ -74,7 +74,7 @@
<dd class="text-right flex items-center justify-end gap-1.5"> <dd class="text-right flex items-center justify-end gap-1.5">
{{ detail.value }} {{ detail.value }}
<a href="{{ detail.link.as_ref().unwrap() }}" target="_blank" rel="noreferrer noopener" class="text-accent hover:text-accent-hover" aria-label="View on map"> <a href="{{ detail.link.as_ref().unwrap() }}" target="_blank" rel="noreferrer noopener" class="text-accent hover:text-accent-hover" aria-label="View on map">
{% call icons::map("h-4 w-4") %} {{ icons::map("h-4 w-4") }}
</a> </a>
</dd> </dd>
{% else if let Some(url) = detail.link %} {% else if let Some(url) = detail.link %}

View file

@ -258,7 +258,7 @@ async fn timeline_page_signals_more_results_when_over_page_size() {
assert!( assert!(
body.contains( body.contains(
"data-next-url=\"/timeline?page=2&amp;page_size=5&amp;sort=occurred-at&amp;dir=desc\"" "data-next-url=\"/timeline?page=2&#38;page_size=5&#38;sort=occurred-at&#38;dir=desc\""
), ),
"Expected loader next-page URL missing from timeline HTML:\n{}", "Expected loader next-page URL missing from timeline HTML:\n{}",
body body