max / quasi
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
5 files changed,
+97 insertions,
-28 deletions
| @@ -1914,34 +1914,28 @@ | |||
| 1914 | 1914 | ||
| 1915 | 1915 | [[package]] | |
| 1916 | 1916 | name = "makeover-layout" | |
| 1917 | - | version = "0.12.0" | |
| 1917 | + | version = "0.14.0" | |
| 1918 | 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1919 | - | checksum = "58edd16523115ed4c9ca6de016693300ac95cf1bb0bd8ccf7fd246213102a7ff" | |
| 1920 | - | ||
| 1921 | - | [[package]] | |
| 1922 | - | name = "makeover-layout" | |
| 1923 | - | version = "0.13.0" | |
| 1924 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1925 | - | checksum = "3e503a11485fe2e17d9e7b0326cb7fef8a9ff0968e1d37d101b45ea08298b0b9" | |
| 1919 | + | checksum = "e08cfaa62476d03061dc86a2befc0c24129399ba37a6f7645fbb72b011abf2f0" | |
| 1926 | 1920 | ||
| 1927 | 1921 | [[package]] | |
| 1928 | 1922 | name = "makeover-touch" | |
| 1929 | - | version = "0.8.1" | |
| 1923 | + | version = "0.8.2" | |
| 1930 | 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1931 | - | checksum = "d9427a7a6890849f5c815a0e77a63faa4791d7a37e7efaff4802bcafa3b8235a" | |
| 1925 | + | checksum = "42481d14654fcbe37a3b2fcfca239448e3c6c7cf474e26a9837b02447fbb1e5c" | |
| 1932 | 1926 | dependencies = [ | |
| 1933 | 1927 | "makeover-geometry", | |
| 1934 | - | "makeover-layout 0.12.0", | |
| 1928 | + | "makeover-layout", | |
| 1935 | 1929 | ] | |
| 1936 | 1930 | ||
| 1937 | 1931 | [[package]] | |
| 1938 | 1932 | name = "makeover-webview" | |
| 1939 | - | version = "0.24.0" | |
| 1933 | + | version = "0.25.1" | |
| 1940 | 1934 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1941 | - | checksum = "6e88bf1eafa0806bbef3002435b91bfb28ce25a989fe484f19eb1e16c58eafc6" | |
| 1935 | + | checksum = "41fb147debeae14c46a48286459712f70cd89d1cce49595fd46a947528c302d5" | |
| 1942 | 1936 | dependencies = [ | |
| 1943 | 1937 | "makeover-geometry", | |
| 1944 | - | "makeover-layout 0.13.0", | |
| 1938 | + | "makeover-layout", | |
| 1945 | 1939 | "makeover-touch", | |
| 1946 | 1940 | ] | |
| 1947 | 1941 | ||
| @@ -2622,7 +2616,7 @@ | |||
| 2622 | 2616 | name = "quasi-router" | |
| 2623 | 2617 | version = "0.1.0" | |
| 2624 | 2618 | dependencies = [ | |
| 2625 | - | "makeover-layout 0.13.0", | |
| 2619 | + | "makeover-layout", | |
| 2626 | 2620 | ] | |
| 2627 | 2621 | ||
| 2628 | 2622 | [[package]] | |
| @@ -2651,7 +2645,7 @@ | |||
| 2651 | 2645 | version = "0.1.0" | |
| 2652 | 2646 | dependencies = [ | |
| 2653 | 2647 | "docengine", | |
| 2654 | - | "makeover-layout 0.13.0", | |
| 2648 | + | "makeover-layout", | |
| 2655 | 2649 | "makeover-webview", | |
| 2656 | 2650 | "quasi-http", | |
| 2657 | 2651 | "quasi-router", |
| @@ -13,4 +13,4 @@ | |||
| 13 | 13 | workspace = true | |
| 14 | 14 | ||
| 15 | 15 | [dependencies] | |
| 16 | - | makeover-layout = "0.13.0" | |
| 16 | + | makeover-layout = "0.14.0" |
| @@ -15,8 +15,8 @@ | |||
| 15 | 15 | [dependencies] | |
| 16 | 16 | quasi-router = { path = "../quasi-router", version = "0.1.0" } | |
| 17 | 17 | quasi-http = { path = "../quasi-http", version = "0.1.0" } | |
| 18 | - | makeover-layout = "0.13.0" | |
| 19 | - | makeover-webview = "0.24.0" | |
| 18 | + | makeover-layout = "0.14.0" | |
| 19 | + | makeover-webview = "0.25.1" | |
| 20 | 20 | # `Node::Rich` carries markdown source and this is what turns it into markup. | |
| 21 | 21 | # Sanitising comes with it, which is why the node can carry what a user typed. | |
| 22 | 22 | # |
| @@ -684,11 +684,24 @@ | |||
| 684 | 684 | // `data-act` for the same reason a button carries it. The row's | |
| 685 | 685 | // `ClickBeside` filter keys on that attribute, so without it a click | |
| 686 | 686 | // on the title would follow the link and open the row underneath. | |
| 687 | + | // Whether the value shares the cell with anything. A cell that is | |
| 688 | + | // only its value says so on the container through `CellPart::Value` | |
| 689 | + | // below, and needs no wrapper; one that mixes has to name its parts | |
| 690 | + | // inside, or the content colour on the cell would reach the tokens | |
| 691 | + | // and the controls beside the text. That is the drift | |
| 692 | + | // makeover-layout 0.14.0 named and makeover-webview 0.25.0 stopped | |
| 693 | + | // emitting. | |
| 694 | + | let mixes = !cell.tokens.is_empty() || !cell.actions.is_empty(); | |
| 695 | + | ||
| 687 | 696 | let mut inner = match &cell.activate { | |
| 688 | 697 | Some(action) => { | |
| 689 | 698 | let (open, close) = control_tag(action); | |
| 690 | 699 | let mut linked = String::from(open); | |
| 691 | - | class_attr(&["cell-activate"], opts, &mut linked); | |
| 700 | + | // `cell-link`, which is what `CellPart::Link` is spelled as | |
| 701 | + | // in the generated stylesheet. It was `cell-activate`, a | |
| 702 | + | // name this crate invented while the shared vocabulary had | |
| 703 | + | // none, and a class with no rule behind it. | |
| 704 | + | class_attr(&["cell-link"], opts, &mut linked); | |
| 692 | 705 | linked.push_str(" data-act"); | |
| 693 | 706 | action_attrs(action, Fires::Click, None, morphs, &mut linked); | |
| 694 | 707 | linked.push('>'); | |
| @@ -696,6 +709,17 @@ | |||
| 696 | 709 | linked.push_str(close); | |
| 697 | 710 | linked | |
| 698 | 711 | } | |
| 712 | + | // An actions-only or tokens-only cell has no value, and an | |
| 713 | + | // empty span for the one it does not have is an element saying | |
| 714 | + | // nothing. | |
| 715 | + | None if mixes && !cell.value.is_empty() => { | |
| 716 | + | let mut valued = String::from("<span"); | |
| 717 | + | class_attr(&["cell-value"], opts, &mut valued); | |
| 718 | + | valued.push('>'); | |
| 719 | + | valued.push_str(&escape(&cell.value)); | |
| 720 | + | valued.push_str("</span>"); | |
| 721 | + | valued | |
| 722 | + | } | |
| 699 | 723 | None => escape(&cell.value), | |
| 700 | 724 | }; | |
| 701 | 725 | // Tags after the text and before the controls, which is the | |
| @@ -711,10 +735,10 @@ | |||
| 711 | 735 | inner.push_str("</span>"); | |
| 712 | 736 | } | |
| 713 | 737 | if !cell.actions.is_empty() { | |
| 714 | - | // Deliberately not `row-actions`: makeover hides that one until | |
| 715 | - | // the row is hovered or focused, which is defensible on a dense | |
| 716 | - | // list and wrong for a table whose last column exists to hold | |
| 717 | - | // the button. A cell's controls are always shown. | |
| 738 | + | // Deliberately not `row-actions`. That was a hover-reveal rule | |
| 739 | + | // until makeover-webview 0.23.0 retired it, and it is a list | |
| 740 | + | // row's class besides: `cell-actions` is the table's own, and | |
| 741 | + | // it carries no colour so a button here is not painted as text. | |
| 718 | 742 | inner.push_str("<span"); | |
| 719 | 743 | class_attr(&["cell-actions"], opts, &mut inner); | |
| 720 | 744 | inner.push('>'); | |
| @@ -726,6 +750,21 @@ | |||
| 726 | 750 | inner | |
| 727 | 751 | }) | |
| 728 | 752 | .collect(); | |
| 753 | + | // The container says what the cell is only when the cell is nothing but its | |
| 754 | + | // value, which is the case where a wrapper span would say nothing the | |
| 755 | + | // container has not already said. Anything else names its parts inside -- | |
| 756 | + | // the anchor is a `cell-link`, the strips are `cell-tokens` and | |
| 757 | + | // `cell-actions` -- because a colour on the container would reach all of | |
| 758 | + | // them, and that is the drift makeover-layout 0.14.0 named. | |
| 759 | + | let parts: Vec<Option<layout::CellPart>> = cells | |
| 760 | + | .values | |
| 761 | + | .iter() | |
| 762 | + | .map(|cell| { | |
| 763 | + | let only_a_value = | |
| 764 | + | cell.tokens.is_empty() && cell.actions.is_empty() && cell.activate.is_none(); | |
| 765 | + | only_a_value.then_some(layout::CellPart::Value) | |
| 766 | + | }) | |
| 767 | + | .collect(); | |
| 729 | 768 | let borrowed: Vec<layout::Column<'_>> = columns | |
| 730 | 769 | .iter() | |
| 731 | 770 | .map(quasi_router::screen::Column::as_layout) | |
| @@ -733,7 +772,12 @@ | |||
| 733 | 772 | let cells: Vec<Emitted<'_>> = borrowed | |
| 734 | 773 | .iter() | |
| 735 | 774 | .zip(filled.iter()) | |
| 736 | - | .map(|(column, value)| Emitted::new(column.name, Markup(value))) | |
| 775 | + | .zip(parts) | |
| 776 | + | .map(|((column, value), part)| Emitted { | |
| 777 | + | column: column.name, | |
| 778 | + | part, | |
| 779 | + | content: Markup(value), | |
| 780 | + | }) | |
| 737 | 781 | .collect(); | |
| 738 | 782 | out.push_str(&cells_html(&borrowed, &cells, opts)); | |
| 739 | 783 |
| @@ -726,9 +726,37 @@ | |||
| 726 | 726 | assert!(html.contains("hx-post=\"/keys/7/delete\"")); | |
| 727 | 727 | assert!(html.contains("cell-actions")); | |
| 728 | 728 | ||
| 729 | - | // Not `row-actions`, which makeover holds at `opacity: 0` until the row is | |
| 730 | - | // hovered. A table's actions column exists to show the button. | |
| 729 | + | // Not `row-actions`, which is a list row's class. A table's actions column | |
| 730 | + | // exists to show the button, and `cell-actions` is the class the generated | |
| 731 | + | // stylesheet gives no colour, so the button is not painted as text. | |
| 731 | 732 | assert!(!html.contains("row-actions")); | |
| 733 | + | ||
| 734 | + | // A cell that is only its value says so on the container, where a wrapper | |
| 735 | + | // span would add an element and no information. | |
| 736 | + | assert!(html.contains("cell-value"), "{html}"); | |
| 737 | + | assert!(!html.contains("<span class=\"cell-value\""), "{html}"); | |
| 738 | + | } | |
| 739 | + | ||
| 740 | + | #[test] | |
| 741 | + | fn a_cell_that_mixes_parts_names_them_inside_rather_than_on_itself() { | |
| 742 | + | // makeover-layout 0.14.0's CellPart, and the reason it is four members | |
| 743 | + | // rather than a flag. A cell holding text AND tokens AND a control is three | |
| 744 | + | // parts in one container: a content colour on the container would reach the | |
| 745 | + | // badge and the button, which is the drift the vocabulary ends. | |
| 746 | + | let html = fragment(&Node::Table { | |
| 747 | + | columns: vec![Column::new("Item").width(layout::Width::Fill)], | |
| 748 | + | rows: vec![Cells::new([Cell::new("Release notes") | |
| 749 | + | .token(Tag::badge("Published")) | |
| 750 | + | .act(Act::new("Remove", Action::post("/blog/7/delete")))])], | |
| 751 | + | }); | |
| 752 | + | ||
| 753 | + | // Each part named where it is, inside the cell. | |
| 754 | + | assert!(html.contains("<span class=\"cell-value\">"), "{html}"); | |
| 755 | + | assert!(html.contains("cell-tokens"), "{html}"); | |
| 756 | + | assert!(html.contains("cell-actions"), "{html}"); | |
| 757 | + | ||
| 758 | + | // And the container claims none of them, or the colour would cascade. | |
| 759 | + | assert!(!html.contains("cell-keeps cell-value"), "{html}"); | |
| 732 | 760 | } | |
| 733 | 761 | ||
| 734 | 762 | #[test] | |
| @@ -877,7 +905,10 @@ | |||
| 877 | 905 | // A read is an anchor with a real href, so middle-click and copy-link work | |
| 878 | 906 | // and the page is still navigable with JS off. | |
| 879 | 907 | assert!(html.contains("href=\"/blog/7\""), "{html}"); | |
| 880 | - | assert!(html.contains("cell-activate"), "{html}"); | |
| 908 | + | // `cell-link`, which is what makeover-layout 0.14.0's `CellPart::Link` is | |
| 909 | + | // spelled as in the generated stylesheet. It was `cell-activate` while this | |
| 910 | + | // crate was inventing the name itself. | |
| 911 | + | assert!(html.contains("cell-link"), "{html}"); | |
| 881 | 912 | assert!(html.contains(">Release notes</a>"), "{html}"); | |
| 882 | 913 | ||
| 883 | 914 | // The value is still text, whatever the value happens to say. |