max / makenotwork
20 files changed,
+820 insertions,
-172 deletions
| @@ -37,16 +37,50 @@ | |||
| 37 | 37 | /// without describing anything. Read it against what moved before treating it | |
| 38 | 38 | /// as a progress figure. | |
| 39 | 39 | /// | |
| 40 | - | /// Two sites are deliberately not converted: the Remove buttons in | |
| 41 | - | /// `partials/link_row.html` and `partials/tag.html` both target | |
| 42 | - | /// `hx-target="closest .<class>"`, and a relative selector is not something | |
| 43 | - | /// `Action::replaces` can say. Filed rather than approximated. | |
| 40 | + | /// 43 to 30: thirteen sites left through glue modules, which is the whole of | |
| 41 | + | /// what a described act can say against a route the description layer does not | |
| 42 | + | /// serve. `session_acts` took the three in `tabs/user_sessions.html`, | |
| 43 | + | /// `promo_code_acts` the two in `partials/promo_codes_list.html`, `repo_acts` | |
| 44 | + | /// the two in `tabs/project_code.html`, `library_acts` the two in | |
| 45 | + | /// `tabs/library_collections.html`, and `clip_acts`, `license_key_act`, | |
| 46 | + | /// `cart_act` and `link_remove_act` one each from `partials/insertion_list.html`, | |
| 47 | + | /// `partials/item_license_keys.html`, `pages/cart.html` and | |
| 48 | + | /// `partials/link_row.html`. `clip_acts` also took the Remove in | |
| 49 | + | /// `partials/placement_list.html`, which the seal never counted: it is | |
| 50 | + | /// destructive with no confirm, and describing it beside its asking twin is | |
| 51 | + | /// where the ruling's separation is proved. | |
| 52 | + | /// | |
| 53 | + | /// `link_row.html` was one of the two the shape recorded as unsayable. | |
| 54 | + | /// `Action::replacing_enclosing` says it now. The other, `partials/tag.html`, | |
| 55 | + | /// stays: its button reads a glyph and takes its name from `aria-label`, and a | |
| 56 | + | /// described act renders its label as text, so converting it would move a | |
| 57 | + | /// prompt and lose a name. | |
| 58 | + | /// | |
| 59 | + | /// What is left splits four ways, none of it a matter of effort. Measured | |
| 60 | + | /// 2026-09-01 by reading the verb, target and swap at each of the 30: | |
| 61 | + | /// | |
| 62 | + | /// - Ten carry no `hx-target` and refresh, reload or navigate through | |
| 63 | + | /// `data-after` glue. `Action::invalidating` emits `data-replaces` and | |
| 64 | + | /// nothing under `frontend/src` performs it, so describing one today would | |
| 65 | + | /// be describing a control that stops working. | |
| 66 | + | /// - Ten answer into a region with `hx-swap="innerHTML"`: the four admin | |
| 67 | + | /// entry partials, both admin dashboards, the broadcast result, and the two | |
| 68 | + | /// dual-serve fragments below. Every described act emits | |
| 69 | + | /// `hx-swap="outerMorph"`, so the container would be replaced by its own | |
| 70 | + | /// contents and the next press would find no target. | |
| 71 | + | /// - Five are submit buttons inside a form that carries the verb. An act is a | |
| 72 | + | /// control with an action of its own, so these convert with their form. | |
| 73 | + | /// - Five are neither: two tag glyphs (above), the two domain rows in | |
| 74 | + | /// `tabs/user_profile.html`, which target `closest .form-section` and also | |
| 75 | + | /// run a `data-after` verb, and the Remove in `tabs/library_purchases.html`, | |
| 76 | + | /// which is a row of a context menu rather than a button standing among | |
| 77 | + | /// others. | |
| 44 | 78 | /// | |
| 45 | 79 | /// `partials/ssh_keys_list.html` and `partials/git_tokens_list.html` are not | |
| 46 | 80 | /// this program's to remove either: they are not tab renderings but the | |
| 47 | 81 | /// fragments `routes::api::ssh_keys` and the git-token routes answer with, and | |
| 48 | 82 | /// the described screens still call those. | |
| 49 | - | const HIGH_WATER: usize = 43; | |
| 83 | + | const HIGH_WATER: usize = 30; | |
| 50 | 84 | ||
| 51 | 85 | /// Every template, recursively. Walked rather than listed for the reason the | |
| 52 | 86 | /// frontend-globals seal walks `frontend/src`: a fence that has to be told about |
| @@ -38,8 +38,9 @@ | |||
| 38 | 38 | use crate::AppState; | |
| 39 | 39 | use crate::auth::SessionUser; | |
| 40 | 40 | ||
| 41 | - | pub mod blog_delete_act; | |
| 42 | 41 | pub mod buyer_contacts; | |
| 42 | + | pub mod cart_act; | |
| 43 | + | pub mod clip_acts; | |
| 43 | 44 | pub mod collections; | |
| 44 | 45 | pub mod creators; | |
| 45 | 46 | pub mod custom_page; | |
| @@ -55,8 +56,11 @@ | |||
| 55 | 56 | pub mod item_files; | |
| 56 | 57 | pub mod item_sales; | |
| 57 | 58 | pub mod item_tabs; | |
| 59 | + | pub mod library_acts; | |
| 58 | 60 | pub mod library_contacts; | |
| 59 | 61 | pub mod library_tabs; | |
| 62 | + | pub mod license_key_act; | |
| 63 | + | pub mod link_remove_act; | |
| 60 | 64 | pub mod media_picker; | |
| 61 | 65 | pub mod payout_summary; | |
| 62 | 66 | pub mod policy; | |
| @@ -66,8 +70,11 @@ | |||
| 66 | 70 | pub mod project_members; | |
| 67 | 71 | pub mod project_overview; | |
| 68 | 72 | pub mod project_tabs; | |
| 73 | + | pub mod promo_code_acts; | |
| 74 | + | pub mod repo_acts; | |
| 69 | 75 | pub mod rich_field; | |
| 70 | 76 | pub mod schedule_field; | |
| 77 | + | pub mod session_acts; | |
| 71 | 78 | pub mod settings_tabs; | |
| 72 | 79 | pub mod shortcuts; | |
| 73 | 80 | pub mod ssh_keys; |
| @@ -84,11 +84,7 @@ | |||
| 84 | 84 | {% endif %} | |
| 85 | 85 | </td> | |
| 86 | 86 | <td> | |
| 87 | - | <button class="btn-secondary small cart-row-btn" | |
| 88 | - | hx-delete="/api/cart/{{ item.item_id }}" | |
| 89 | - | hx-target="#cart-row-{{ item.item_id }}" | |
| 90 | - | hx-swap="outerHTML" | |
| 91 | - | hx-confirm="Remove this item from your cart?">Remove</button> | |
| 87 | + | {{ crate::quasi::cart_act::html(item.item_id)|safe }} | |
| 92 | 88 | </td> | |
| 93 | 89 | </tr> | |
| 94 | 90 | {% endfor %} |
| @@ -29,11 +29,7 @@ | |||
| 29 | 29 | <td>{{ ins.created_at }}</td> | |
| 30 | 30 | <td class="insertion-list-actions"> | |
| 31 | 31 | <button type="button" class="btn btn-xs insertion-list-action-btn" data-title="{{ ins.title }}" data-action="onInsertionRename" data-arg="{{ ins.id }}">Rename</button> | |
| 32 | - | <button type="button" class="btn btn-xs btn-danger insertion-list-action-btn" | |
| 33 | - | hx-delete="/api/insertions/{{ ins.id }}" | |
| 34 | - | hx-target="#insertion-library" | |
| 35 | - | hx-swap="outerHTML" | |
| 36 | - | hx-confirm="Delete this clip? It will be removed from all items using it.">Delete</button> | |
| 32 | + | {{ crate::quasi::clip_acts::delete_html(ins.id)|safe }} | |
| 37 | 33 | </td> | |
| 38 | 34 | </tr> | |
| 39 | 35 | {% endfor %} |
| @@ -34,13 +34,7 @@ | |||
| 34 | 34 | <td>{{ key.created_at }}</td> | |
| 35 | 35 | <td> | |
| 36 | 36 | {% if !key.is_revoked %} | |
| 37 | - | <button class="btn btn-sm btn-danger" | |
| 38 | - | hx-post="/api/keys/{{ key.id }}/revoke" | |
| 39 | - | hx-target="#license-keys-list" | |
| 40 | - | hx-swap="outerHTML" | |
| 41 | - | hx-confirm="Revoke this license key? All activations will be deactivated."> | |
| 42 | - | Revoke | |
| 43 | - | </button> | |
| 37 | + | {{ crate::quasi::license_key_act::html(key.id)|safe }} | |
| 44 | 38 | {% endif %} | |
| 45 | 39 | </td> | |
| 46 | 40 | </tr> |
| @@ -1,4 +1,4 @@ | |||
| 1 | - | <div class="link-row" data-id="{{ id }}"> | |
| 1 | + | <div class="link-row" data-row data-id="{{ id }}"> | |
| 2 | 2 | <div class="link-order-buttons"> | |
| 3 | 3 | <button type="button" class="order-btn" data-action="onMoveLink" data-arg="-1" title="Move up">▲</button> | |
| 4 | 4 | <button type="button" class="order-btn" data-action="onMoveLink" data-arg="1" title="Move down">▼</button> | |
| @@ -8,9 +8,5 @@ | |||
| 8 | 8 | <button class="btn-secondary link-edit-btn" data-action="onEditLink">Edit</button> | |
| 9 | 9 | <button class="btn-primary link-save-btn hidden" data-action="onSaveLink">Save</button> | |
| 10 | 10 | <button class="btn-secondary link-cancel-btn hidden" data-action="onCancelLink">Cancel</button> | |
| 11 | - | <button class="btn-danger link-remove-btn" | |
| 12 | - | hx-delete="/api/links/{{ id }}" | |
| 13 | - | hx-target="closest .link-row" | |
| 14 | - | hx-swap="outerHTML" | |
| 15 | - | hx-confirm="Remove this link?">Remove</button> | |
| 11 | + | <span class="link-remove-btn">{{ crate::quasi::link_remove_act::html(id)|safe }}</span> | |
| 16 | 12 | </div> |
| @@ -23,10 +23,7 @@ | |||
| 23 | 23 | <td>{{ p.position }}</td> | |
| 24 | 24 | <td>{% if let Some(offset) = p.offset_display %}{{ offset }}{% else %}-{% endif %}</td> | |
| 25 | 25 | <td class="placement-list-actions"> | |
| 26 | - | <button type="button" class="btn btn-xs btn-danger placement-list-remove" | |
| 27 | - | hx-delete="/api/item-insertions/{{ p.id }}" | |
| 28 | - | hx-target="#placement-list" | |
| 29 | - | hx-swap="outerHTML">Remove</button> | |
| 26 | + | {{ crate::quasi::clip_acts::remove_html(p.id)|safe }} | |
| 30 | 27 | </td> | |
| 31 | 28 | </tr> | |
| 32 | 29 | {% endfor %} |
| @@ -63,13 +63,7 @@ | |||
| 63 | 63 | <td class="promo-codes-actions"> | |
| 64 | 64 | <button class="btn-secondary small promo-codes-edit-btn" | |
| 65 | 65 | data-action="togglePromoEditRow">Edit</button> | |
| 66 | - | <button class="btn btn-sm btn-danger" | |
| 67 | - | hx-delete="/api/promo-codes/{{ code.id }}" | |
| 68 | - | hx-target="#promo-codes-list" | |
| 69 | - | hx-swap="outerHTML" | |
| 70 | - | hx-confirm="Delete this code? It will no longer be usable."> | |
| 71 | - | Delete | |
| 72 | - | </button> | |
| 66 | + | {{ crate::quasi::promo_code_acts::html(code.id)|safe }} | |
| 73 | 67 | </td> | |
| 74 | 68 | </tr> | |
| 75 | 69 | <tr class="hidden promo-codes-edit-row"> | |
| @@ -105,11 +99,7 @@ | |||
| 105 | 99 | </table> | |
| 106 | 100 | ||
| 107 | 101 | <div class="promo-codes-bulk-row"> | |
| 108 | - | <button class="btn-secondary small promo-codes-bulk-btn" | |
| 109 | - | hx-delete="/api/promo-codes/expired" | |
| 110 | - | hx-target="#promo-codes-list" | |
| 111 | - | hx-swap="outerHTML" | |
| 112 | - | hx-confirm="Delete all expired promo codes?">Delete all expired</button> | |
| 102 | + | {{ crate::quasi::promo_code_acts::expired_html()|safe }} | |
| 113 | 103 | </div> | |
| 114 | 104 | {% endif %} | |
| 115 | 105 | </div> |
| @@ -17,17 +17,13 @@ | |||
| 17 | 17 | </thead> | |
| 18 | 18 | <tbody> | |
| 19 | 19 | {% for c in collections %} | |
| 20 | - | <tr> | |
| 20 | + | <tr data-row> | |
| 21 | 21 | <td><a href="/c/{{ username }}/{{ c.slug }}">{{ c.title }}</a></td> | |
| 22 | 22 | <td>{{ c.item_count }}</td> | |
| 23 | 23 | <td>{% if c.is_public %}<span class="badge">Public</span>{% else %}<span class="badge is-faded">Private</span>{% endif %}</td> | |
| 24 | 24 | <td>{{ c.created_at }}</td> | |
| 25 | 25 | <td> | |
| 26 | - | <button class="btn-secondary small btn-compact" | |
| 27 | - | hx-delete="/api/collections/{{ c.id }}" | |
| 28 | - | hx-confirm="Delete this collection?" | |
| 29 | - | hx-target="closest tr" | |
| 30 | - | hx-swap="outerHTML">Delete</button> | |
| 26 | + | {{ crate::quasi::library_acts::delete_collection_html(c.id)|safe }} | |
| 31 | 27 | </td> | |
| 32 | 28 | </tr> | |
| 33 | 29 | {% endfor %} | |
| @@ -97,11 +93,7 @@ | |||
| 97 | 93 | hx-post="/api/cart/{{ item.item_id }}" | |
| 98 | 94 | hx-swap="none" | |
| 99 | 95 | data-after="mark-added">Add to Cart</button> | |
| 100 | - | <button class="btn-secondary small btn-compact" | |
| 101 | - | hx-post="/api/wishlists/{{ item.item_id }}" | |
| 102 | - | hx-target="#wishlist-row-{{ item.item_id }}" | |
| 103 | - | hx-swap="outerHTML swap:0.2s" | |
| 104 | - | hx-confirm="Remove from wishlist?">Remove</button> | |
| 96 | + | {{ crate::quasi::library_acts::remove_from_wishlist_html(item.item_id)|safe }} | |
| 105 | 97 | </td> | |
| 106 | 98 | </tr> | |
| 107 | 99 | {% endfor %} |
| @@ -13,11 +13,7 @@ | |||
| 13 | 13 | <div class="proj-code-repo"> | |
| 14 | 14 | <div class="proj-code-repo-row"> | |
| 15 | 15 | <a href="/git/{{ repo.name }}" class="proj-code-repo-link">{{ repo.name }}</a> | |
| 16 | - | <button class="btn-danger" | |
| 17 | - | hx-delete="/api/projects/{{ project.id }}/repos/{{ repo.name }}" | |
| 18 | - | hx-target="#git-repos-section" | |
| 19 | - | hx-swap="outerHTML" | |
| 20 | - | hx-confirm="Unlink '{{ repo.name }}' from this project?">Unlink</button> | |
| 16 | + | {{ crate::quasi::repo_acts::unlink_html(project.id, repo.name)|safe }} | |
| 21 | 17 | </div> | |
| 22 | 18 | {% if !repo.collaborators.is_empty() %} | |
| 23 | 19 | <div class="proj-code-collab-block"> | |
| @@ -25,11 +21,7 @@ | |||
| 25 | 21 | {% for c in &repo.collaborators %} | |
| 26 | 22 | <div class="proj-code-collab-row"> | |
| 27 | 23 | <span class="proj-code-collab-name">@{{ c.username }}{% if !c.can_push %} <span class="proj-code-collab-readonly">(read only)</span>{% endif %}</span> | |
| 28 | - | <button class="btn-danger small" | |
| 29 | - | hx-delete="/api/repos/{{ repo.id }}/collaborators/{{ c.user_id }}" | |
| 30 | - | hx-target="#git-repos-section" | |
| 31 | - | hx-swap="outerHTML" | |
| 32 | - | hx-confirm="Remove @{{ c.username }} from {{ repo.name }}?">Remove</button> | |
| 24 | + | {{ crate::quasi::repo_acts::remove_collaborator_html(repo.id, repo.name, c.user_id, c.username)|safe }} | |
| 33 | 25 | </div> | |
| 34 | 26 | {% endfor %} | |
| 35 | 27 | </div> |
| @@ -31,18 +31,10 @@ | |||
| 31 | 31 | <td> | |
| 32 | 32 | {% if let Some(current_id) = current_session_id %} | |
| 33 | 33 | {% if s.id != *current_id %} | |
| 34 | - | <button class="btn-secondary small" | |
| 35 | - | hx-delete="/api/users/me/sessions/{{ s.id }}" | |
| 36 | - | hx-target="#sessions-list" | |
| 37 | - | hx-swap="outerHTML" | |
| 38 | - | hx-confirm="Sign out this device?">Sign out</button> | |
| 34 | + | {{ crate::quasi::session_acts::html(s.id)|safe }} | |
| 39 | 35 | {% endif %} | |
| 40 | 36 | {% else %} | |
| 41 | - | <button class="btn-secondary small" | |
| 42 | - | hx-delete="/api/users/me/sessions/{{ s.id }}" | |
| 43 | - | hx-target="#sessions-list" | |
| 44 | - | hx-swap="outerHTML" | |
| 45 | - | hx-confirm="Sign out this device?">Sign out</button> | |
| 37 | + | {{ crate::quasi::session_acts::html(s.id)|safe }} | |
| 46 | 38 | {% endif %} | |
| 47 | 39 | </td> | |
| 48 | 40 | </tr> | |
| @@ -52,11 +44,7 @@ | |||
| 52 | 44 | ||
| 53 | 45 | {% if sessions.len() > 1 %} | |
| 54 | 46 | <div class="mt-section"> | |
| 55 | - | <button class="btn-secondary" | |
| 56 | - | hx-delete="/api/users/me/sessions" | |
| 57 | - | hx-target="#sessions-list" | |
| 58 | - | hx-swap="outerHTML" | |
| 59 | - | hx-confirm="Sign out all other devices?">Sign out all other devices</button> | |
| 47 | + | {{ crate::quasi::session_acts::all_others_html()|safe }} | |
| 60 | 48 | </div> | |
| 61 | 49 | {% endif %} | |
| 62 | 50 | {% endif %} |
| @@ -1,91 +1,0 @@ | |||
| 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 | - | } |