Commit

Author:

Hash:

Timestamp:

+186 -90 +/-15 browse

Kevin Schoon [me@kevinschoon.com]

864b8617d7e29563262f8a28bbc537c9c0aaddb4

Tue, 07 Oct 2025 19:40:23 +0000 (1.0 years ago)

rework theme a bit, eliminate config page, style fixes
1diff --git a/ayllu/Cargo.toml b/ayllu/Cargo.toml
2index ae5e2c7..06484ed 100644
3--- a/ayllu/Cargo.toml
4+++ b/ayllu/Cargo.toml
5 @@ -41,7 +41,7 @@ serde_json = "1.0.140"
6 tree-sitter = "0.25.6"
7 libloading = "0.8.8"
8 rss = "2.0.12"
9- axum = { version = "0.8.4", features = ["macros"] }
10+ axum = { version = "0.8.4", features = ["macros", "query"] }
11 axum-extra = { version = "0.10.1", features = ["cookie", "typed-header"] }
12 tower-http = { version = "0.6.6", features = ["tracing", "add-extension", "trace", "normalize-path"] }
13 tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
14 diff --git a/ayllu/src/web2/config.rs b/ayllu/src/web2/config.rs
15index a8a2a87..1750f9f 100644
16--- a/ayllu/src/web2/config.rs
17+++ b/ayllu/src/web2/config.rs
18 @@ -1,8 +1,9 @@
19 use serde::{Deserialize, Serialize};
20
21 // Client side configuration via cookies
22- #[derive(Clone, Deserialize, Serialize)]
23+ #[derive(Clone, Default, Debug, Deserialize, Serialize)]
24 pub struct Config {
25+ pub current_url: Option<String>,
26 pub theme: Option<String>,
27 pub items_per_page: usize,
28 }
29 diff --git a/ayllu/src/web2/extractors/config.rs b/ayllu/src/web2/extractors/config.rs
30index 6f78236..4117c6d 100644
31--- a/ayllu/src/web2/extractors/config.rs
32+++ b/ayllu/src/web2/extractors/config.rs
33 @@ -27,6 +27,7 @@ where
34 items_per_page: cookies.get("items-per-page").map_or(100, |cookie| {
35 cookie.value().to_string().parse::<usize>().unwrap_or(100)
36 }),
37+ current_url: None,
38 }))
39 }
40 }
41 diff --git a/ayllu/src/web2/middleware/template.rs b/ayllu/src/web2/middleware/template.rs
42index 8f88a66..0d154ac 100644
43--- a/ayllu/src/web2/middleware/template.rs
44+++ b/ayllu/src/web2/middleware/template.rs
45 @@ -1,20 +1,48 @@
46 use axum::{
47- extract::{Request, State},
48+ extract::{Query, Request, State},
49 middleware::Next,
50 response::Response,
51 };
52+ use serde::Deserialize;
53
54 use crate::{
55 config::Config,
56- web2::template::{Base, DEFAULT_LOGO, DEFAULT_RSS_ICON},
57+ web2::{
58+ extractors::config::ConfigReader,
59+ template::{Base, ControlPanel, DEFAULT_LOGO, DEFAULT_RSS_ICON},
60+ },
61 };
62
63- pub async fn middleware(State(config): State<Config>, mut req: Request, next: Next) -> Response {
64- req.extensions_mut().insert(Base {
65+ #[derive(Deserialize)]
66+ pub struct Params {
67+ pub menu: Option<u8>,
68+ }
69+
70+ pub async fn middleware(
71+ State(config): State<Config>,
72+ Query(params): Query<Params>,
73+ ConfigReader(user_config): ConfigReader,
74+ mut req: Request,
75+ next: Next,
76+ ) -> Response {
77+ let request_uri = req.uri();
78+ let template_base = Base {
79 subpath_mode: config.subpath_mode,
80 logo: DEFAULT_LOGO.to_string(),
81 feed_icon: DEFAULT_RSS_ICON.to_string(),
82+ user_config,
83+ control_panel: ControlPanel {
84+ themes: config
85+ .web
86+ .themes
87+ .iter()
88+ .map(|theme| theme.name.clone())
89+ .collect(),
90+ },
91+ path: request_uri.path().to_string(),
92+ menu_open: params.menu.is_some_and(|open| open > 0),
93 ..Default::default()
94- });
95+ };
96+ req.extensions_mut().insert(template_base);
97 next.run(req).await
98 }
99 diff --git a/ayllu/src/web2/routes/about.rs b/ayllu/src/web2/routes/about.rs
100index d855030..825784a 100644
101--- a/ayllu/src/web2/routes/about.rs
102+++ b/ayllu/src/web2/routes/about.rs
103 @@ -1,6 +1,6 @@
104 use askama::Template;
105 use axum::{extract::Extension, response::Html};
106- use comrak::{markdown_to_html_with_plugins, ComrakOptions, ComrakPlugins};
107+ use comrak::{ComrakOptions, ComrakPlugins, markdown_to_html_with_plugins};
108
109 use crate::config::Config;
110 use crate::highlight::TreeSitterAdapter;
111 @@ -23,7 +23,6 @@ pub async fn serve(
112 let mut plugins = ComrakPlugins::default();
113 plugins.render.codefence_syntax_highlighter = Some(&adapter);
114 let blurb = markdown_to_html_with_plugins(&cfg.blurb, &options, &plugins);
115- base.nav_elements = crate::web2::navigation::global("about", false);
116 base.current_time = timeutil::timestamp();
117 Ok(Html(
118 PageTemplate {
119 diff --git a/ayllu/src/web2/routes/config.rs b/ayllu/src/web2/routes/config.rs
120index 04cd51d..ff1c83c 100644
121--- a/ayllu/src/web2/routes/config.rs
122+++ b/ayllu/src/web2/routes/config.rs
123 @@ -1,14 +1,13 @@
124 use askama::Template;
125 use axum::{
126 extract::{Extension, Form},
127- response::{Html, Redirect},
128+ response::Redirect,
129 };
130
131 use axum_extra::extract::cookie::{Cookie, CookieJar};
132
133 use crate::web2::config::Config;
134 use crate::web2::error::Error;
135- use crate::web2::extractors::config::ConfigReader;
136 use crate::{config::Config as SystemConfig, web2::template::Base};
137
138 #[derive(Template)]
139 @@ -21,34 +20,12 @@ struct Index<'a> {
140 config: Config,
141 }
142
143- pub async fn serve(
144- Extension(cfg): Extension<SystemConfig>,
145- ConfigReader(config): ConfigReader,
146- Extension(mut base): Extension<Base>,
147- ) -> Result<Html<String>, Error> {
148- base.with_nav(&[("about", "/about", false), ("config", "/config", true)]);
149- base.current_time = timeutil::timestamp();
150- Ok(Html(
151- Index {
152- base,
153- themes: cfg
154- .web
155- .themes
156- .iter()
157- .map(|theme| theme.name.clone())
158- .collect(),
159- default_theme: &cfg.web.default_theme,
160- config,
161- }
162- .render()?,
163- ))
164- }
165-
166 pub async fn update(
167 jar: CookieJar,
168 Extension(cfg): Extension<SystemConfig>,
169 Form(updates): Form<Config>,
170 ) -> Result<(CookieJar, Redirect), Error> {
171+ let redirect_to = format!("{}?menu=1", updates.current_url.unwrap_or("/".to_string()));
172 Ok((
173 jar.add(Cookie::new(
174 "theme",
175 @@ -58,6 +35,6 @@ pub async fn update(
176 "items-per-page",
177 updates.items_per_page.to_string(),
178 )),
179- Redirect::to("/config"),
180+ Redirect::to(&redirect_to),
181 ))
182 }
183 diff --git a/ayllu/src/web2/routes/index.rs b/ayllu/src/web2/routes/index.rs
184index b044db1..c2f3134 100644
185--- a/ayllu/src/web2/routes/index.rs
186+++ b/ayllu/src/web2/routes/index.rs
187 @@ -86,7 +86,6 @@ pub async fn index(
188 });
189 }
190 base.title = String::from("Repositories");
191- base.nav_elements = crate::web2::navigation::global("index", false);
192 base.current_time = timeutil::timestamp();
193 let page = IndexPageTemplate { base, collections };
194 Ok(Html(page.render()?))
195 @@ -116,7 +115,6 @@ pub async fn collection(
196 let entry = entry.unwrap();
197 let repositories = load_repositories(entry.path.as_path()).await?;
198 base.title = format!("{collection:?}");
199- base.nav_elements = crate::web2::navigation::global("index", false);
200 base.current_time = timeutil::timestamp();
201 let page = CollectionPageTemplate {
202 base,
203 diff --git a/ayllu/src/web2/server.rs b/ayllu/src/web2/server.rs
204index 7f957cd..10f761d 100644
205--- a/ayllu/src/web2/server.rs
206+++ b/ayllu/src/web2/server.rs
207 @@ -4,8 +4,8 @@ use std::sync::Arc;
208 use std::{collections::HashMap, path::Path};
209
210 use axum::{
211- body::Body, extract::Request, middleware::from_fn_with_state, routing, routing::MethodFilter,
212- Extension, Router, ServiceExt,
213+ Extension, Router, ServiceExt, body::Body, extract::Request, middleware::from_fn_with_state,
214+ routing, routing::MethodFilter,
215 };
216 use tokio::net::TcpListener;
217 use tower::Layer;
218 @@ -116,7 +116,7 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
219 .route("/rss/1w.xml", routing::get(rss::feed_1w))
220 .route("/rss/1m.xml", routing::get(rss::feed_1m))
221 .route("/about", routing::get(about::serve))
222- .route("/config", routing::get(config::serve).post(config::update)),
223+ .route("/config", routing::post(config::update)),
224 )
225 .nest(
226 "/0",
227 diff --git a/ayllu/src/web2/template.rs b/ayllu/src/web2/template.rs
228index c11724d..f7a6918 100644
229--- a/ayllu/src/web2/template.rs
230+++ b/ayllu/src/web2/template.rs
231 @@ -1,6 +1,8 @@
232 use const_css_minify::minify;
233 use serde::{Deserialize, Serialize};
234
235+ use crate::web2::config::Config as UserConfig;
236+
237 pub type Items = Vec<(String, String, bool)>;
238
239 // TODO: Make configurable
240 @@ -37,6 +39,12 @@ macro_rules! with_preamble {
241 }};
242 }
243
244+ /// Config panel options
245+ #[derive(Clone, Serialize, Deserialize, Debug, Default)]
246+ pub struct ControlPanel {
247+ pub themes: Vec<String>,
248+ }
249+
250 /// top-level theme options available in all pages
251 #[derive(Clone, Serialize, Deserialize, Debug)]
252 pub struct Base {
253 @@ -56,20 +64,13 @@ pub struct Base {
254 pub nav_elements: Items,
255 pub logo: String,
256 pub feed_icon: String,
257+ pub user_config: UserConfig,
258+ pub control_panel: ControlPanel,
259+ pub menu_open: bool,
260 pub version_info: &'static str,
261 pub rust_version: &'static str,
262 }
263
264- impl Base {
265- pub fn with_nav(&mut self, items: &[(&str, &str, bool)]) {
266- self.nav_elements = Vec::from_iter(
267- items
268- .iter()
269- .map(|(name, link, active)| (name.to_string(), link.to_string(), *active)),
270- );
271- }
272- }
273-
274 impl Default for Base {
275 fn default() -> Self {
276 Self {
277 @@ -89,6 +90,9 @@ impl Default for Base {
278 nav_elements: Default::default(),
279 logo: Default::default(),
280 feed_icon: Default::default(),
281+ user_config: Default::default(),
282+ control_panel: Default::default(),
283+ menu_open: Default::default(),
284 version_info: env!("AYLLU_COMMIT"),
285 rust_version: env!("AYLLU_TOOLCHAIN_VERSION"),
286 }
287 @@ -98,7 +102,7 @@ impl Default for Base {
288 pub mod filters {
289
290 use file_mode::Mode;
291- use time::{format_description::well_known, OffsetDateTime};
292+ use time::{OffsetDateTime, format_description::well_known};
293
294 use crate::web2::util;
295
296 diff --git a/ayllu/templates/base.html b/ayllu/templates/base.html
297index f00c0ab..4532538 100644
298--- a/ayllu/templates/base.html
299+++ b/ayllu/templates/base.html
300 @@ -15,11 +15,11 @@
301 <body>
302 <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1404468 -->
303 <script>0</script>
304- {% block navigation %}
305- {% include "nav.html" %}
306- {% endblock %}
307 <main>
308 <div class="container">
309+ {% block navigation %}
310+ {% include "nav.html" %}
311+ {% endblock %}
312 {% block content %}{% endblock %}
313 </div>
314 </main>
315 diff --git a/ayllu/templates/nav.html b/ayllu/templates/nav.html
316index 8ca9205..79b390f 100644
317--- a/ayllu/templates/nav.html
318+++ b/ayllu/templates/nav.html
319 @@ -1,12 +1,61 @@
320 <nav>
321- <a href="{%- if base.subpath_mode -%}/browse{%- else -%}/{%- endif -%}">
322- <div class="logo">{{ base.logo | safe }}</div>
323- </a>
324- <ul>
325- {% for item in base.nav_elements %}
326- <li {% if item.2 %}class="active"{% endif %}>
327- <a href="{{ item.1 }}">{{ item.0 }}</a>
328- </li>
329- {% endfor %}
330- </ul>
331+ <input id="panel-toggle" type="checkbox" name="menu" {%- if base.menu_open %} checked {%- endif %} />
332+ <div class="group">
333+ <div id="left">
334+ <ul class="outer">
335+ <li class="tilde"><a href="/">~</a></li>
336+ <li><a href="/about">About</a></li>
337+ <li>
338+ <label for="panel-toggle">Config</label>
339+ </li>
340+ </ul>
341+ </div>
342+ <div id="right">
343+ <ul class="outer">
344+ {% for item in base.nav_elements %}
345+ <li {% if item.2 %}class="active"{% endif %}>
346+ <a href="{{ item.1 }}">{{ item.0 }}</a>
347+ </li>
348+ {% endfor %}
349+ </ul>
350+ </div>
351+ </div>
352+ <div id="control-panel">
353+ <form action="/config" method="post">
354+ <label for="theme">items-per-page</label>
355+ <select id="items_per_page" name="items_per_page" required>
356+ <option value="50"
357+ {%- if base.user_config.items_per_page == 50 -%}
358+ selected
359+ {%- endif -%}>50</option>
360+ <option value="100"
361+ {%- if base.user_config.items_per_page == 100 -%}
362+ selected
363+ {%- endif -%}>100</option>
364+ <option value="150"
365+ {%- if base.user_config.items_per_page == 150 -%}
366+ selected
367+ {%- endif -%}>150</option>
368+ <option value="200"
369+ {%- if base.user_config.items_per_page == 200 -%}
370+ selected
371+ {%- endif -%}>200</option>
372+ </select>
373+ <label for="theme">theme</label>
374+ <select id="theme" name="theme" required>
375+ {% for theme in base.control_panel.themes %}
376+ <option value="{{ theme }}"
377+ {% if let Some(current_theme) = base.user_config.theme %}
378+ {%- if theme == current_theme -%}
379+ selected
380+ {%- endif -%}
381+ {%- endif -%}>{{ theme }}</option>
382+ {% endfor %}
383+ </select>
384+ <label for="current_url"></label>
385+ <input id="current_url" name="current_url" type="text" value="{{base.path}}"/>
386+ <button type="submit">Submit</button>
387+ </form>
388+
389+ </div>
390 </nav>
391 diff --git a/ayllu/themes/base.css b/ayllu/themes/base.css
392index cf04145..ef13515 100644
393--- a/ayllu/themes/base.css
394+++ b/ayllu/themes/base.css
395 @@ -40,35 +40,57 @@ table {
396 min-width: 100%;
397 }
398
399- nav {
400- display: flex;
401- justify-content: space-between;
402- align-items: center;
403- padding: 1em;
404- border-radius: 5px;
405+ .tilde {
406+ transform: scale(2);
407+ font-weight: bold;
408 }
409
410- nav .logo {
411- width: 100px;
412- font-size: 20px;
413- font-weight: bold;
414+ .logo {
415+ float: left;
416+ width: 25px;
417+ height: 25px;
418+ margin-bottom: -5px;
419 }
420
421- nav ul {
422- list-style-type: none;
423- margin: 0;
424- padding: 0;
425+
426+ nav div.group {
427 display: flex;
428- }
429+ justify-content: space-between;
430+
431+ ul.outer {
432+ list-style-type: none;
433+ margin: 0;
434+ padding: 0;
435+ display: flex;
436
437- nav ul li {
438- margin-left: 20px;
439+ li {
440+ margin: 5px;
441+ }
442+ }
443 }
444
445 nav ul li a {
446 text-decoration: none;
447 }
448
449+ div#control-panel {
450+ display: none;
451+ padding: .5em;
452+ }
453+
454+ input,
455+ ul.submenu {
456+ display: none;
457+ }
458+
459+ input:checked~div#control-panel {
460+ display: block;
461+ }
462+
463+ input:checked~ul.submenu {
464+ display: block;
465+ }
466+
467 span.hint {
468 padding: 4px;
469 font-weight: bold;
470 diff --git a/ayllu/themes/catppuccin.css b/ayllu/themes/catppuccin.css
471index 6ea4bf7..2522fe1 100644
472--- a/ayllu/themes/catppuccin.css
473+++ b/ayllu/themes/catppuccin.css
474 @@ -73,14 +73,14 @@
475 --ayllu-negative: var(--ctp-latte-red);
476 --ayllu-section: var(--ctp-latte-overlay2);
477 --ayllu-section-title: var(--ctp-latte-text);
478- --ayllu-section-background: var(--ctp-latte-mantle);
479+ --ayllu-section-background: var(--ctp-latte-crust);
480 --ayllu-section-border: var(--ctp-latte-text);
481 --ayllu-section-link: var(--ctp-latte-mauve);
482 --ayllu-section-header-link: var(--ctp-latte-blue);
483
484 --ayllu-selectable-text: var(--ctp-latte-subtext1);
485- --ayllu-selectable-text-background: var(--ctp-latte-surface0);
486- --ayllu-selectable-text-background-badge: var(--ctp-latte-lavender);
487+ --ayllu-selectable-text-background: var(--ctp-latte-mantle);
488+ --ayllu-selectable-text-background-badge: var(--ctp-latte-base);
489
490 --ayllu-code-viewer: var(--ctp-latte-text);
491 --ayllu-code-viewer-background: white;
492 @@ -106,12 +106,19 @@
493 --ayllu-color: var(--ctp-mocha-text);
494 --ayllu-background: var(--ctp-mocha-base);
495 --ayllu-link: var(--ctp-mocha-sapphire);
496+
497+ --ayllu-control-panel: var(--ctp-mocha-text);
498+ --ayllu-control-panel-background: var(--ctp-mocha-overlay0);
499+
500 --ayllu-section: var(--ctp-mocha-text);
501- --ayllu-section-border: var(--ctp-mocha-maroon);
502+ --ayllu-section-border: var(--ctp-mocha-mauve);
503 --ayllu-section-title: var(--ctp-mocha-text);
504+ --ayllu-section-header-link: var(--ctp-mocha-blue);
505 --ayllu-section-background: var(--ctp-mocha-surface0);
506 --ayllu-background-secondary: var(--ctp-mocha-surface1);
507 --ayllu-background-tertiary: var(--ctp-mocha-surface2);
508+ --ayllu-selectable-text-background: var(--ctp-latte-mantle);
509+ --ayllu-selectable-text-background-badge: var(--ctp-latte-peach);
510
511 --ayllu-code-viewer: var(--nord5);
512 --ayllu-code-viewer-background: var(--nord1);
513 diff --git a/ayllu/themes/colorize.css b/ayllu/themes/colorize.css
514index 6587dbe..b626b0e 100644
515--- a/ayllu/themes/colorize.css
516+++ b/ayllu/themes/colorize.css
517 @@ -18,6 +18,11 @@ nav {
518 }
519 }
520
521+ div#control-panel {
522+ color: var(--ayllu-control-panel);
523+ background-color: var(--ayllu-control-panel-background);
524+ }
525+
526 section.title>a {
527 color: var(--ayllu-section-header-link);
528 }
529 @@ -80,9 +85,9 @@ section#commit-summary>section>pre {
530 background-color: var(--ayllu-background-tertiary);
531 }
532
533- /* section.info-bar.single { */
534- /* border-color: var(--nord2); */
535- /* } */
536+ section.info-bar.single {
537+ border-color: var(--ayllu-section-border);
538+ }
539
540 .positive {
541 text-shadow: 1px 1px 5px var(--ayllu-positive);
542 diff --git a/ayllu/themes/nord.css b/ayllu/themes/nord.css
543index ded02f8..6c5ca48 100644
544--- a/ayllu/themes/nord.css
545+++ b/ayllu/themes/nord.css
546 @@ -39,11 +39,14 @@
547 --ayllu-negative: var(--nord11);
548 --ayllu-section: var(--nord0);
549 --ayllu-section-header: var(--nord1);
550- --ayllu-section-background: var(--nord2);
551+ --ayllu-section-background: var(--nord4);
552 --ayllu-section-border: var(--nord3);
553 --ayllu-section-link: var(--nord5);
554 --ayllu-section-header-link: var(--nord4);
555
556+ --ayllu-control-panel: var(--nord0);
557+ --ayllu-control-panel-background: var(--nord4);
558+
559 --ayllu-selectable-text: var(--nord3);
560 --ayllu-selectable-text-background: var(--nord9);
561 --ayllu-selectable-text-background-badge: var(--nord7);
562 @@ -71,7 +74,9 @@
563 @media (prefers-color-scheme: dark) {
564 --ayllu-color: var(--nord6);
565 --ayllu-background: var(--nord0);
566- --ayllu-link: var(--nord14);
567+ --ayllu-link: var(--nord8);
568+ --ayllu-section: var(--nord4);
569+ --ayllu-section-background: var(--nord1);
570 --ayllu-section-header-border: var(--nord6);
571 --ayllu-background-secondary: var(--nord2);
572 --ayllu-background-tertiary: var(--nord3);