max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
6 files changed,
+258 insertions,
-43 deletions
| @@ -53,10 +53,13 @@ | |||
| 53 | 53 | /// | |
| 54 | 54 | /// The Payments tab leaves an empty div here and fills it on `revealed`, so | |
| 55 | 55 | /// this region is the whole of what the section is, not a pane it shares. | |
| 56 | - | const REGION: &str = "contacts-section"; | |
| 56 | + | /// `pub` so the seam's mount can name it: a panel served from a residual | |
| 57 | + | /// answers no `Response`, so the region it retargets is stated at the mount | |
| 58 | + | /// rather than by the router. `library_contacts::REGION` is `pub` already. | |
| 59 | + | pub const REGION: &str = "contacts-section"; | |
| 57 | 60 | ||
| 58 | 61 | /// One buyer who chose to share their email, as the screen needs it. | |
| 59 | - | pub struct BuyerView { | |
| 62 | + | pub(crate) struct BuyerView { | |
| 60 | 63 | username: String, | |
| 61 | 64 | email: String, | |
| 62 | 65 | purchases: String, | |
| @@ -64,8 +67,9 @@ | |||
| 64 | 67 | last_purchase: String, | |
| 65 | 68 | } | |
| 66 | 69 | ||
| 67 | - | /// The section. | |
| 68 | - | pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> { | |
| 70 | + | /// The one read this section makes, for the mount that serves it from a | |
| 71 | + | /// residual. | |
| 72 | + | pub(crate) fn reading(viewer: &Viewer) -> Result<Vec<BuyerView>, RouteError> { | |
| 69 | 73 | let contacts = viewer | |
| 70 | 74 | .block_on(db::transactions::get_seller_contacts( | |
| 71 | 75 | &viewer.app.db, | |
| @@ -74,7 +78,7 @@ | |||
| 74 | 78 | .map_err(|_| RouteError::internal("your contacts could not be read"))?; | |
| 75 | 79 | ||
| 76 | 80 | let currency = viewer.reader()?.settlement_currency; | |
| 77 | - | let buyers: Vec<BuyerView> = contacts | |
| 81 | + | Ok(contacts | |
| 78 | 82 | .into_iter() | |
| 79 | 83 | .map(|contact| BuyerView { | |
| 80 | 84 | username: contact.username, | |
| @@ -83,14 +87,18 @@ | |||
| 83 | 87 | spent: crate::formatting::format_revenue(contact.total_spent_cents, currency), | |
| 84 | 88 | last_purchase: contact.last_purchase_at.format("%b %-d, %Y").to_string(), | |
| 85 | 89 | }) | |
| 86 | - | .collect(); | |
| 90 | + | .collect()) | |
| 91 | + | } | |
| 87 | 92 | ||
| 88 | - | Ok(Response::fragment(REGION, pane(&buyers))) | |
| 93 | + | /// The section. | |
| 94 | + | pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> { | |
| 95 | + | Ok(Response::fragment(REGION, pane(&reading(viewer)?))) | |
| 89 | 96 | } | |
| 90 | 97 | ||
| 91 | 98 | declare! { | |
| 92 | 99 | /// Everything inside the section. | |
| 93 | - | shape pane(buyers: &[BuyerView]) -> Node; | |
| 100 | + | #[staged] | |
| 101 | + | pub(crate) shape pane(buyers: &[BuyerView]) -> Node; | |
| 94 | 102 | ||
| 95 | 103 | region REGION as Pane { | |
| 96 | 104 | section "Shared Contacts ({buyers.len()})"; | |
| @@ -123,6 +131,7 @@ | |||
| 123 | 131 | /// bytes are not, and `layout::Awaiting` takes a measurement rather than a | |
| 124 | 132 | /// stand-in for one. | |
| 125 | 133 | #[must_use] | |
| 134 | + | #[constant] | |
| 126 | 135 | shape export() -> Node; | |
| 127 | 136 | ||
| 128 | 137 | // Through `export_act` rather than spelled again here. This screen had the | |
| @@ -141,6 +150,7 @@ | |||
| 141 | 150 | /// and every buyer contributes the same five cells, so there is no seam for | |
| 142 | 151 | /// a heading and a cell to drift across. Nothing is paged either, so no | |
| 143 | 152 | /// `more`: this is a whole set the handler already counted. | |
| 153 | + | #[staged] | |
| 144 | 154 | shape table(buyers: &[BuyerView]) -> Node; | |
| 145 | 155 | ||
| 146 | 156 | table { | |
| @@ -187,6 +197,21 @@ | |||
| 187 | 197 | Webview::new().with_shell(viewer.shell()) | |
| 188 | 198 | } | |
| 189 | 199 | ||
| 200 | + | /// One buyer as the tests draw it. | |
| 201 | + | /// | |
| 202 | + | /// Module-level rather than inside `mod tests` because `quasi::residuals` needs | |
| 203 | + | /// one too, and `BuyerView` is this module's own type. Test-only. | |
| 204 | + | #[cfg(test)] | |
| 205 | + | pub(crate) fn sample(username: &str) -> BuyerView { | |
| 206 | + | BuyerView { | |
| 207 | + | username: username.into(), | |
| 208 | + | email: format!("{username}@example.com"), | |
| 209 | + | purchases: "3".into(), | |
| 210 | + | spent: "$42.00".into(), | |
| 211 | + | last_purchase: "Aug 10, 2026".into(), | |
| 212 | + | } | |
| 213 | + | } | |
| 214 | + | ||
| 190 | 215 | #[cfg(test)] | |
| 191 | 216 | mod tests { | |
| 192 | 217 | use quasi_axum::Serves; | |
| @@ -194,15 +219,7 @@ | |||
| 194 | 219 | ||
| 195 | 220 | use super::*; | |
| 196 | 221 | ||
| 197 | - | fn buyer(username: &str) -> BuyerView { | |
| 198 | - | BuyerView { | |
| 199 | - | username: username.into(), | |
| 200 | - | email: format!("{username}@example.com"), | |
| 201 | - | purchases: "3".into(), | |
| 202 | - | spent: "$42.00".into(), | |
| 203 | - | last_purchase: "Aug 10, 2026".into(), | |
| 204 | - | } | |
| 205 | - | } | |
| 222 | + | use super::sample as buyer; | |
| 206 | 223 | ||
| 207 | 224 | fn render(node: &Node) -> String { | |
| 208 | 225 | Webview::new().fragment(node) |
| @@ -54,6 +54,7 @@ | |||
| 54 | 54 | /// call site and the byte count never is, and `layout::Awaiting` takes a | |
| 55 | 55 | /// measurement rather than a stand-in for one. | |
| 56 | 56 | #[must_use] | |
| 57 | + | #[constant] | |
| 57 | 58 | pub shape act(route: &str, filename: &str) -> Node; | |
| 58 | 59 | ||
| 59 | 60 | include control("Export CSV", route, filename); |
| @@ -51,7 +51,7 @@ | |||
| 51 | 51 | pub const WRITES: &[(Method, &str, super::Screen)] = &[(Method::Delete, REVOKE, revoke)]; | |
| 52 | 52 | ||
| 53 | 53 | /// One buyer who chose to share their email, as the screen needs it. | |
| 54 | - | pub struct BuyerView { | |
| 54 | + | pub(crate) struct BuyerView { | |
| 55 | 55 | username: String, | |
| 56 | 56 | email: String, | |
| 57 | 57 | purchases: String, | |
| @@ -60,14 +60,18 @@ | |||
| 60 | 60 | } | |
| 61 | 61 | ||
| 62 | 62 | /// One creator this reader has shared their own email with. | |
| 63 | - | pub struct SharedView { | |
| 63 | + | pub(crate) struct SharedView { | |
| 64 | 64 | seller_id: String, | |
| 65 | 65 | username: String, | |
| 66 | 66 | name: String, | |
| 67 | 67 | } | |
| 68 | 68 | ||
| 69 | 69 | /// The tab. | |
| 70 | - | pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> { | |
| 70 | + | /// The one read this tab makes, for the mount that serves it from a residual. | |
| 71 | + | /// | |
| 72 | + | /// Both lists together, because the pane is one region and reading them apart | |
| 73 | + | /// would be two round trips where the screen has one. | |
| 74 | + | pub(crate) fn reading(viewer: &Viewer) -> Result<(Vec<BuyerView>, Vec<SharedView>), RouteError> { | |
| 71 | 75 | let user_id = viewer.reader()?.id; | |
| 72 | 76 | ||
| 73 | 77 | let shared = viewer | |
| @@ -119,6 +123,11 @@ | |||
| 119 | 123 | }) | |
| 120 | 124 | .collect(); | |
| 121 | 125 | ||
| 126 | + | Ok((buyers, shared)) | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | pub fn screen(viewer: &Viewer, _request: Request) -> Result<Response, RouteError> { | |
| 130 | + | let (buyers, shared) = reading(viewer)?; | |
| 122 | 131 | Ok(Response::fragment(REGION, pane(&buyers, &shared))) | |
| 123 | 132 | } | |
| 124 | 133 | ||
| @@ -160,7 +169,8 @@ | |||
| 160 | 169 | /// two conditions and returns early. Said as guards, the two halves each | |
| 161 | 170 | /// carry their own, and the both-empty case is the one where neither is | |
| 162 | 171 | /// placed and the empty line is. | |
| 163 | - | shape pane(buyers: &[BuyerView], shared: &[SharedView]) -> Node; | |
| 172 | + | #[staged] | |
| 173 | + | pub(crate) shape pane(buyers: &[BuyerView], shared: &[SharedView]) -> Node; | |
| 164 | 174 | ||
| 165 | 175 | region REGION as Pane { | |
| 166 | 176 | empty "No contacts yet." when buyers.is_empty() and shared.is_empty(); | |
| @@ -184,6 +194,7 @@ | |||
| 184 | 194 | /// declaration and every buyer contributes the same five cells, so naming | |
| 185 | 195 | /// each column would buy nothing a reader cannot already see. Nothing is | |
| 186 | 196 | /// paged, so no `more`: this is a whole set the handler already counted. | |
| 197 | + | #[staged] | |
| 187 | 198 | shape buyers_table(buyers: &[BuyerView]) -> Node; | |
| 188 | 199 | ||
| 189 | 200 | table { | |
| @@ -238,6 +249,7 @@ | |||
| 238 | 249 | /// and the pairing would be less obvious than the order already makes it. | |
| 239 | 250 | /// Both cells are drawn for every creator. Nothing is paged, so no `more`: | |
| 240 | 251 | /// this is a whole set the handler already counted. | |
| 252 | + | #[staged] | |
| 241 | 253 | shape shared_table(shared: &[SharedView]) -> Node; | |
| 242 | 254 | ||
| 243 | 255 | table { | |
| @@ -282,28 +294,37 @@ | |||
| 282 | 294 | Webview::new().with_shell(viewer.shell()) | |
| 283 | 295 | } | |
| 284 | 296 | ||
| 297 | + | /// One buyer as the tests draw it. | |
| 298 | + | /// | |
| 299 | + | /// Module-level rather than inside `mod tests` because `quasi::residuals` needs | |
| 300 | + | /// one too, and these are this module's own types. Test-only. | |
| 301 | + | #[cfg(test)] | |
| 302 | + | pub(crate) fn sample_buyer(username: &str) -> BuyerView { | |
| 303 | + | BuyerView { | |
| 304 | + | username: username.into(), | |
| 305 | + | email: format!("{username}@example.com"), | |
| 306 | + | purchases: "3".into(), | |
| 307 | + | spent: "$42.00".into(), | |
| 308 | + | last_purchase: "Aug 10, 2026".into(), | |
| 309 | + | } | |
| 310 | + | } | |
| 311 | + | ||
| 312 | + | /// One creator this reader shares an address with, as the tests draw it. | |
| 313 | + | #[cfg(test)] | |
| 314 | + | pub(crate) fn sample_creator(id: &str, username: &str, name: &str) -> SharedView { | |
| 315 | + | SharedView { | |
| 316 | + | seller_id: id.into(), | |
| 317 | + | username: username.into(), | |
| 318 | + | name: name.into(), | |
| 319 | + | } | |
| 320 | + | } | |
| 321 | + | ||
| 285 | 322 | #[cfg(test)] | |
| 286 | 323 | mod tests { | |
| 287 | 324 | use super::*; | |
| 288 | 325 | use quasi_axum::Serves; | |
| 289 | 326 | ||
| 290 | - | fn buyer(username: &str) -> BuyerView { | |
| 291 | - | BuyerView { | |
| 292 | - | username: username.into(), | |
| 293 | - | email: format!("{username}@example.com"), | |
| 294 | - | purchases: "3".into(), | |
| 295 | - | spent: "$42.00".into(), | |
| 296 | - | last_purchase: "Aug 10, 2026".into(), | |
| 297 | - | } | |
| 298 | - | } | |
| 299 | - | ||
| 300 | - | fn creator(id: &str, username: &str, name: &str) -> SharedView { | |
| 301 | - | SharedView { | |
| 302 | - | seller_id: id.into(), | |
| 303 | - | username: username.into(), | |
| 304 | - | name: name.into(), | |
| 305 | - | } | |
| 306 | - | } | |
| 327 | + | use super::{sample_buyer as buyer, sample_creator as creator}; | |
| 307 | 328 | ||
| 308 | 329 | fn render(node: &quasi_router::Node) -> String { | |
| 309 | 330 | Webview::new().fragment(node) |
| @@ -520,11 +520,19 @@ | |||
| 520 | 520 | ), | |
| 521 | 521 | ( | |
| 522 | 522 | library_contacts::PATH, | |
| 523 | - | mount( | |
| 523 | + | served_panel_mount( | |
| 524 | 524 | app, | |
| 525 | - | library_contacts::screen, | |
| 525 | + | library_contacts::REGION, | |
| 526 | 526 | library_contacts::WRITES, | |
| 527 | 527 | library_contacts::renderer, | |
| 528 | + | |viewer, _| { | |
| 529 | + | let (buyers, shared) = library_contacts::reading(viewer)?; | |
| 530 | + | Ok(library_contacts::pane_serve( | |
| 531 | + | &residuals::LIBRARY_CONTACTS, | |
| 532 | + | &buyers, | |
| 533 | + | &shared, | |
| 534 | + | )) | |
| 535 | + | }, | |
| 528 | 536 | ), | |
| 529 | 537 | ), | |
| 530 | 538 | // One module, two screens: the library tab and the settings section are | |
| @@ -548,7 +556,18 @@ | |||
| 548 | 556 | ), | |
| 549 | 557 | ( | |
| 550 | 558 | buyer_contacts::PATH, | |
| 551 | - | mount(app, buyer_contacts::screen, &[], buyer_contacts::renderer), | |
| 559 | + | served_panel_mount( | |
| 560 | + | app, | |
| 561 | + | buyer_contacts::REGION, | |
| 562 | + | &[], | |
| 563 | + | buyer_contacts::renderer, | |
| 564 | + | |viewer, _| { | |
| 565 | + | Ok(buyer_contacts::pane_serve( | |
| 566 | + | &residuals::BUYER_CONTACTS, | |
| 567 | + | &buyer_contacts::reading(viewer)?, | |
| 568 | + | )) | |
| 569 | + | }, | |
| 570 | + | ), | |
| 552 | 571 | ), | |
| 553 | 572 | ( | |
| 554 | 573 | user_analytics::PATH, |
| @@ -89,6 +89,12 @@ | |||
| 89 | 89 | ("FORUMS_SETTINGS", |plan| { | |
| 90 | 90 | super::forum_memberships::settings_pane_staged(plan) | |
| 91 | 91 | }), | |
| 92 | + | ("BUYER_CONTACTS", |plan| { | |
| 93 | + | super::buyer_contacts::pane_staged(plan) | |
| 94 | + | }), | |
| 95 | + | ("LIBRARY_CONTACTS", |plan| { | |
| 96 | + | super::library_contacts::pane_staged(plan) | |
| 97 | + | }), | |
| 92 | 98 | ] | |
| 93 | 99 | } | |
| 94 | 100 | ||
| @@ -619,6 +625,52 @@ | |||
| 619 | 625 | } | |
| 620 | 626 | } | |
| 621 | 627 | ||
| 628 | + | /// The two contact panes fill to what the renderer builds. | |
| 629 | + | /// | |
| 630 | + | /// Both are tables behind guards, and the library's is two of them: buyers | |
| 631 | + | /// and shared-with, each with its own heading and its own empty state, and | |
| 632 | + | /// a third state where neither is placed and one line says so. Every | |
| 633 | + | /// combination is filled, because a residual that had lost one table's | |
| 634 | + | /// branch would still pass on the shapes where that table is absent. | |
| 635 | + | #[test] | |
| 636 | + | fn the_contact_residuals_fill_to_what_the_renderer_builds() { | |
| 637 | + | use quasi_axum::Serves as _; | |
| 638 | + | ||
| 639 | + | for count in [0, 1, 3] { | |
| 640 | + | let buyers: Vec<_> = (0..count) | |
| 641 | + | .map(|n| crate::quasi::buyer_contacts::sample(&format!("buyer{n}"))) | |
| 642 | + | .collect(); | |
| 643 | + | assert_eq!( | |
| 644 | + | crate::quasi::buyer_contacts::pane_serve(&BUYER_CONTACTS, &buyers), | |
| 645 | + | Webview::new().fragment(&crate::quasi::buyer_contacts::pane(&buyers)), | |
| 646 | + | "buyer contacts, {count} buyers", | |
| 647 | + | ); | |
| 648 | + | } | |
| 649 | + | ||
| 650 | + | for buyers in [0, 2] { | |
| 651 | + | for shared in [0, 2] { | |
| 652 | + | let held: Vec<_> = (0..buyers) | |
| 653 | + | .map(|n| crate::quasi::library_contacts::sample_buyer(&format!("buyer{n}"))) | |
| 654 | + | .collect(); | |
| 655 | + | let with: Vec<_> = (0..shared) | |
| 656 | + | .map(|n| { | |
| 657 | + | crate::quasi::library_contacts::sample_creator( | |
| 658 | + | &format!("{n}"), | |
| 659 | + | &format!("creator{n}"), | |
| 660 | + | &format!("Creator {n}"), | |
| 661 | + | ) | |
| 662 | + | }) | |
| 663 | + | .collect(); | |
| 664 | + | ||
| 665 | + | assert_eq!( | |
| 666 | + | crate::quasi::library_contacts::pane_serve(&LIBRARY_CONTACTS, &held, &with), | |
| 667 | + | Webview::new().fragment(&crate::quasi::library_contacts::pane(&held, &with)), | |
| 668 | + | "library contacts, {buyers} buyers and {shared} shared", | |
| 669 | + | ); | |
| 670 | + | } | |
| 671 | + | } | |
| 672 | + | } | |
| 673 | + | ||
| 622 | 674 | /// Every screen the roster names is checked above. | |
| 623 | 675 | /// | |
| 624 | 676 | /// The gap this closes is the one a table opens: a screen added to | |
| @@ -634,8 +686,8 @@ | |||
| 634 | 686 | fn every_screen_on_the_seam_is_checked() { | |
| 635 | 687 | /// Screens with their own filling test: `/use-cases`, `/fan-plus`, | |
| 636 | 688 | /// `/c/{username}/{slug}`, `/dashboard/export`, `/git/{owner}`, and the | |
| 637 | - | /// two forum panes. | |
| 638 | - | const HOLED: usize = 7; | |
| 689 | + | /// two forum panes, and the two contact panes. | |
| 690 | + | const HOLED: usize = 9; | |
| 639 | 691 | ||
| 640 | 692 | assert_eq!( | |
| 641 | 693 | roster().len(), |
| @@ -284,4 +284,109 @@ | |||
| 284 | 284 | ])), | |
| 285 | 285 | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"settings-forums-anchored\" data-menu=\"anchored\" hidden></div></div>")), | |
| 286 | 286 | ]); | |
| 287 | + | ||
| 288 | + | pub static BUYER_CONTACTS: ::quasi_router::stage::Residual = | |
| 289 | + | ::quasi_router::stage::Residual::compiled(&[ | |
| 290 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div id=\"contacts-section\" class=\"region pane\"><h2 class=\"heading\">Shared Contacts (")), | |
| 291 | + | ::quasi_router::stage::Op::Hole { scope: 0, id: 0 }, | |
| 292 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed(")</h2><p class=\"text\">Buyers who opted to share their email at checkout. They can revoke sharing from their library.</p>")), | |
| 293 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 294 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"placeholder\" data-state=\"empty\" role=\"status\" aria-live=\"polite\"><p class=\"placeholder-text\">No shared contacts yet. When buyers opt to share their email at checkout, they will appear here.</p></div>")), | |
| 295 | + | ||
| 296 | + | ])), | |
| 297 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 298 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<button type=\"button\" class=\"button\" data-act data-saves=\"contacts.csv\" hx-post=\"/api/export/contacts\" hx-swap=\"outerMorph\" hx-disable=\"this\" data-awaiting=\"indeterminate\">Export CSV</button>")), | |
| 299 | + | ||
| 300 | + | ])), | |
| 301 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 302 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"table\" class=\"table\"><div role=\"row\" class=\"table-head\"><span role=\"columnheader\" class=\"table-heading col-Username cell-content cell-keeps\">Username</span><span role=\"columnheader\" class=\"table-heading col-Email cell-fill cell-keeps\">Email</span><span role=\"columnheader\" class=\"table-heading col-Purchases cell-content cell-drops-next\">Purchases</span><span role=\"columnheader\" class=\"table-heading col-Total-Spent cell-content cell-drops-next\">Total Spent</span><span role=\"columnheader\" class=\"table-heading col-Last-Purchase cell-content cell-drops-first\">Last Purchase</span></div>")), | |
| 303 | + | ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[ | |
| 304 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row><div class=\"cell col-Username cell-content cell-keeps\"><a class=\"cell-link\" data-act href=\"/u/")), | |
| 305 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 1 }, | |
| 306 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\">")), | |
| 307 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 0 }, | |
| 308 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col-Email cell-fill cell-keeps cell-value\">")), | |
| 309 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 2 }, | |
| 310 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Purchases cell-content cell-drops-next cell-value\">")), | |
| 311 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 3 }, | |
| 312 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Total-Spent cell-content cell-drops-next cell-value\">")), | |
| 313 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 4 }, | |
| 314 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Last-Purchase cell-content cell-drops-first cell-value\">")), | |
| 315 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 5 }, | |
| 316 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div></div>")), | |
| 317 | + | ||
| 318 | + | ])), | |
| 319 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")), | |
| 320 | + | ||
| 321 | + | ])), | |
| 322 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"contacts-section-anchored\" data-menu=\"anchored\" hidden></div></div>")), | |
| 323 | + | ]); | |
| 324 | + | ||
| 325 | + | pub static LIBRARY_CONTACTS: ::quasi_router::stage::Residual = | |
| 326 | + | ::quasi_router::stage::Residual::compiled(&[ | |
| 327 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div id=\"library-contacts\" class=\"region pane\">")), | |
| 328 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 329 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"placeholder\" data-state=\"empty\" role=\"status\" aria-live=\"polite\"><p class=\"placeholder-text\">No contacts yet.</p></div>")), | |
| 330 | + | ||
| 331 | + | ])), | |
| 332 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 333 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<h2 class=\"heading\">Your Buyers (")), | |
| 334 | + | ::quasi_router::stage::Op::Hole { scope: 0, id: 0 }, | |
| 335 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed(")</h2>")), | |
| 336 | + | ||
| 337 | + | ])), | |
| 338 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 339 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<p class=\"text\">Buyers who opted to share their email with you at purchase time.</p>")), | |
| 340 | + | ||
| 341 | + | ])), | |
| 342 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 343 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"table\" class=\"table\"><div role=\"row\" class=\"table-head\"><span role=\"columnheader\" class=\"table-heading col-Username cell-content cell-keeps\">Username</span><span role=\"columnheader\" class=\"table-heading col-Email cell-fill cell-keeps\">Email</span><span role=\"columnheader\" class=\"table-heading col-Purchases cell-content cell-drops-next\">Purchases</span><span role=\"columnheader\" class=\"table-heading col-Total-Spent cell-content cell-drops-next\">Total Spent</span><span role=\"columnheader\" class=\"table-heading col-Last-Purchase cell-content cell-drops-first\">Last Purchase</span></div>")), | |
| 344 | + | ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[ | |
| 345 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row><div class=\"cell col-Username cell-content cell-keeps\"><a class=\"cell-link\" data-act href=\"/u/")), | |
| 346 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 1 }, | |
| 347 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\">")), | |
| 348 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 0 }, | |
| 349 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col-Email cell-fill cell-keeps\"><a class=\"cell-link\" data-act href=\"mailto:")), | |
| 350 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 3 }, | |
| 351 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\" target=\"_blank\" rel=\"noopener noreferrer\">")), | |
| 352 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 2 }, | |
| 353 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col-Purchases cell-content cell-drops-next cell-value\">")), | |
| 354 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 4 }, | |
| 355 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Total-Spent cell-content cell-drops-next cell-value\">")), | |
| 356 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 5 }, | |
| 357 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div><div class=\"cell col-Last-Purchase cell-content cell-drops-first cell-value\">")), | |
| 358 | + | ::quasi_router::stage::Op::Hole { scope: 1, id: 6 }, | |
| 359 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div></div>")), | |
| 360 | + | ||
| 361 | + | ])), | |
| 362 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")), | |
| 363 | + | ||
| 364 | + | ])), | |
| 365 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 366 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<h2 class=\"heading\">Shared With</h2>")), | |
| 367 | + | ||
| 368 | + | ])), | |
| 369 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 370 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<p class=\"text\">You've shared your email with these creators. You can revoke sharing at any time.</p>")), | |
| 371 | + | ||
| 372 | + | ])), | |
| 373 | + | ::quasi_router::stage::Op::Branch(::std::borrow::Cow::Borrowed(&[ | |
| 374 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"table\" class=\"table\"><div role=\"row\" class=\"table-head\"><span role=\"columnheader\" class=\"table-heading col-Creator cell-fill cell-keeps\">Creator</span><span role=\"columnheader\" class=\"table-heading col- cell-content cell-keeps\"></span></div>")), | |
| 375 | + | ::quasi_router::stage::Op::Loop(::std::borrow::Cow::Borrowed(&[ | |
| 376 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div role=\"row\" class=\"table-row\" data-row><div class=\"cell col-Creator cell-fill cell-keeps\"><a class=\"cell-link\" data-act href=\"/u/")), | |
| 377 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 1 }, | |
| 378 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\">")), | |
| 379 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 0 }, | |
| 380 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</a></div><div class=\"cell col- cell-content cell-keeps\"><span class=\"cell-actions\"><button type=\"button\" class=\"button\" data-tone=\"danger\" data-act hx-delete=\"/library/tabs/contacts/revoke/")), | |
| 381 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 2 }, | |
| 382 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("\" hx-confirm=\"Revoke contact sharing with ")), | |
| 383 | + | ::quasi_router::stage::Op::Hole { scope: 2, id: 3 }, | |
| 384 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("?\" hx-swap=\"outerMorph\" hx-disable=\"this\" data-awaiting=\"indeterminate\">Revoke</button></span></div></div>")), | |
| 385 | + | ||
| 386 | + | ])), | |
| 387 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("</div>")), | |
| 388 | + | ||
| 389 | + | ])), | |
| 390 | + | ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed("<div class=\"anchored\" id=\"library-contacts-anchored\" data-menu=\"anchored\" hidden></div></div>")), | |
| 391 | + | ]); | |
| 287 | 392 |