Skip to main content

max / makenotwork

4.2 KB · 114 lines History Blame Raw
1 //! The Askama entry point for the two described removals on a reader's library.
2 //!
3 //! Shape 8 (`b279b9eb`), through the glue-module ruling `27d5e5b8`. Both
4 //! destroy a row and both ask.
5 //!
6 //! # Two spellings of "where the answer goes", and why they differ
7 //!
8 //! Neither `DELETE /api/collections/{id}` nor `POST /api/wishlists/{item}` is
9 //! served by the description layer, so each control names what it replaces. A
10 //! wishlist row has an id in the page already (`wishlist-row-{item}`) and says
11 //! it with [`Action::replacing`]. A collection row has none: the template
12 //! reached it with `closest tr`, which is the case
13 //! [`Action::replacing_enclosing`] was added for, and it needs no parameter
14 //! because the act is inside the row it removes. The row carries `data-row` in
15 //! Askama for the same reason a described row does.
16 //!
17 //! The wishlist row asked for `hx-swap="outerHTML swap:0.2s"`, and every
18 //! described act swaps `outerMorph` with no delay, so that row stops playing
19 //! the global `.htmx-swapping` fade. A swap timing is a fact about one
20 //! renderer, which is exactly the kind of thing a description does not hold;
21 //! every other described control in the app already lost it.
22 //!
23 //! Inventing an id for the collection row was the other option and is the wrong
24 //! one: `version_delete_act` minted one because a version row has an identity
25 //! in the database the template was simply not writing down, and a `<tr>` in a
26 //! list of collections has no such fact going spare.
27 //!
28 //! Ids arrive as `impl Display` rather than `&str`: the caller is Askama and
29 //! the server's ids are typed newtypes over `Uuid`, so asking for a string
30 //! would push a `to_string()` into every call site to say what `format!`
31 //! already says here.
32
33 use std::fmt;
34
35 use makeover_layout::Tone;
36 use quasi_router::screen::Act;
37 use quasi_router::{Action, Node};
38
39 /// Delete one collection, replacing the row it sits in.
40 #[must_use]
41 pub fn delete_collection_html(collection_id: impl fmt::Display) -> String {
42 fragment(
43 Act::new(
44 "Delete",
45 Action::delete(format!("/api/collections/{collection_id}")).replacing_enclosing(),
46 )
47 .tone(Tone::Danger)
48 .confirm("Delete this collection?"),
49 )
50 }
51
52 /// Take one item off the wishlist.
53 ///
54 /// A post rather than a delete, because the route toggles: the same address
55 /// puts an item on the list. The description says what this press does, which
56 /// is what the reader is being asked about.
57 #[must_use]
58 pub fn remove_from_wishlist_html(item_id: impl fmt::Display) -> String {
59 fragment(
60 Act::new(
61 "Remove",
62 Action::post(format!("/api/wishlists/{item_id}"))
63 .replacing(format!("wishlist-row-{item_id}")),
64 )
65 .tone(Tone::Danger)
66 .confirm("Remove from wishlist?"),
67 )
68 }
69
70 /// No shell and no region: this lands inside a document Askama has already
71 /// built, which is what every glue module here does.
72 fn fragment(act: Act) -> String {
73 use quasi_axum::Serves as _;
74
75 quasi_webview::Webview::new().fragment(&Node::Act(act))
76 }
77
78 #[cfg(test)]
79 mod tests {
80 /// The row it sits in, rather than an id the table would have had to grow.
81 #[test]
82 fn deleting_a_collection_replaces_the_row_it_is_in() {
83 let html = super::delete_collection_html("c1");
84
85 assert!(
86 html.contains(r#"hx-delete="/api/collections/c1""#),
87 "{html}"
88 );
89 assert!(html.contains(r#"hx-target="closest [data-row]""#), "{html}");
90 assert!(
91 html.contains(r#"hx-confirm="Delete this collection?""#),
92 "{html}"
93 );
94 }
95
96 #[test]
97 fn removing_from_the_wishlist_names_the_row_the_page_already_has() {
98 let html = super::remove_from_wishlist_html("i7");
99
100 assert!(html.contains(r#"hx-post="/api/wishlists/i7""#), "{html}");
101 assert!(html.contains(r##"hx-target="#wishlist-row-i7""##), "{html}");
102 assert!(
103 html.contains(r#"hx-confirm="Remove from wishlist?""#),
104 "{html}"
105 );
106 }
107
108 #[test]
109 fn both_removals_say_they_are_dangerous() {
110 assert!(super::delete_collection_html("c1").contains(r#"data-tone="danger""#));
111 assert!(super::remove_from_wishlist_html("i7").contains(r#"data-tone="danger""#));
112 }
113 }
114