Skip to main content

max / makenotwork

3.8 KB · 92 lines History Blame Raw
1 //! The Askama entry point for a described "delete this blog post" button.
2 //!
3 //! Shape 8's first conversion that did not have to wait for a screen
4 //! (`b279b9eb`), and the second thing the glue-module ruling bought
5 //! (`27d5e5b8`, reasoning in [`super::widgets`]). The same button was written
6 //! twice, in `templates/partials/tabs/project_content.html` and
7 //! `templates/partials/tabs/project_blog.html`, over the same route with the
8 //! same prompt, in two tabs of the same dashboard. Described once, called from
9 //! both.
10 //!
11 //! **One caller today**, which is not a reason to inline it back:
12 //! `project_content.html`'s own copy becomes a described element in its turn,
13 //! and this is where it goes.
14 //!
15 //! # What the description says that the markup did not
16 //!
17 //! That the act is destructive, and what to ask before doing it.
18 //! [`Act::confirm`] is that, and `hx-confirm` was it: one of the 50 sites the
19 //! shape exists to move. The difference is where the sentence lives. A prompt
20 //! in an attribute is a fact about one template; a prompt on the act is a fact
21 //! about the call, so a terminal asks y/n and a touch host raises an action
22 //! sheet without either being told about this button.
23 //!
24 //! `Tone::Danger` for the same reason. The templates said it with
25 //! `class="danger-text"`, which is the same claim in a spelling no other
26 //! renderer can read.
27 //!
28 //! # Why the row id is a parameter
29 //!
30 //! The answer replaces the row it was pressed in, and `/api/blog/{id}` is a
31 //! plain API route the description layer does not serve — it answers an
32 //! `HX-Trigger` toast and an empty body — so it cannot name what it changed and
33 //! the control has to. See [`Action::replaces`]: this is exactly the case that
34 //! member exists for, rather than the misuse it warns about.
35 //!
36 //! Both templates already give the row a real id (`post-row-{id}`), which is
37 //! what makes these two describable when their neighbours are not. The Remove
38 //! buttons in `partials/link_row.html` and `partials/tag.html` carry
39 //! `hx-target="closest .link-row"` and `hx-target="closest .tag"`, and a
40 //! relative selector is not something [`Action::replaces`] can say; the tag one
41 //! additionally reads `×` and gets its name from `aria-label`, which is
42 //! quasicoherent `aad33ecc`. Both are left hand-written and filed rather than
43 //! approximated.
44
45 use makeover_layout::Tone;
46 use quasi_router::screen::Act;
47 use quasi_router::{Action, Node};
48
49 /// The delete button for one post's row.
50 ///
51 /// `id` is the blog post's, and it names both the route and the row: the two
52 /// are the same fact, so the caller states it once.
53 #[must_use]
54 pub fn html(id: &str) -> String {
55 use quasi_axum::Serves as _;
56
57 let act = Act::new(
58 "Delete",
59 Action::delete(format!("/api/blog/{id}")).replacing(format!("post-row-{id}")),
60 )
61 .tone(Tone::Danger)
62 .confirm("Delete this blog post?");
63
64 quasi_webview::Webview::new().fragment(&Node::Act(act))
65 }
66
67 #[cfg(test)]
68 mod tests {
69 /// The three things the two templates said between them, said once: where
70 /// it goes, what it replaces, and that it is destructive.
71 #[test]
72 fn a_delete_names_its_route_its_row_and_its_prompt() {
73 let html = super::html("42");
74
75 assert!(html.contains(r#"hx-delete="/api/blog/42""#), "{html}");
76 assert!(html.contains(r##"hx-target="#post-row-42""##), "{html}");
77 assert!(
78 html.contains(r#"hx-confirm="Delete this blog post?""#),
79 "{html}"
80 );
81 }
82
83 /// The tone, which is the half that could not survive before `35756077`.
84 /// `danger-text` in a class attribute said this to the stylesheet only.
85 #[test]
86 fn a_delete_says_it_is_dangerous_in_a_word_every_renderer_reads() {
87 let html = super::html("42");
88
89 assert!(html.contains(r#"data-tone="danger""#), "{html}");
90 }
91 }
92