max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
19 files changed,
+926 insertions,
-307 deletions
| @@ -2875,7 +2875,7 @@ | |||
| 2875 | 2875 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | |
| 2876 | 2876 | dependencies = [ | |
| 2877 | 2877 | "libc", | |
| 2878 | - | "windows-sys 0.52.0", | |
| 2878 | + | "windows-sys 0.59.0", | |
| 2879 | 2879 | ] | |
| 2880 | 2880 | ||
| 2881 | 2881 | [[package]] | |
| @@ -4811,7 +4811,7 @@ | |||
| 4811 | 4811 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4812 | 4812 | checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" | |
| 4813 | 4813 | dependencies = [ | |
| 4814 | - | "windows-sys 0.61.2", | |
| 4814 | + | "windows-sys 0.59.0", | |
| 4815 | 4815 | ] | |
| 4816 | 4816 | ||
| 4817 | 4817 | [[package]] | |
| @@ -5722,7 +5722,7 @@ | |||
| 5722 | 5722 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5723 | 5723 | checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" | |
| 5724 | 5724 | dependencies = [ | |
| 5725 | - | "windows-sys 0.61.2", | |
| 5725 | + | "windows-sys 0.59.0", | |
| 5726 | 5726 | ] | |
| 5727 | 5727 | ||
| 5728 | 5728 | [[package]] | |
| @@ -6636,7 +6636,7 @@ | |||
| 6636 | 6636 | ||
| 6637 | 6637 | [[package]] | |
| 6638 | 6638 | name = "quasi-axum" | |
| 6639 | - | version = "0.91.1" | |
| 6639 | + | version = "0.92.0" | |
| 6640 | 6640 | dependencies = [ | |
| 6641 | 6641 | "axum", | |
| 6642 | 6642 | "http 1.4.2", | |
| @@ -6647,7 +6647,7 @@ | |||
| 6647 | 6647 | ||
| 6648 | 6648 | [[package]] | |
| 6649 | 6649 | name = "quasi-basics" | |
| 6650 | - | version = "0.91.1" | |
| 6650 | + | version = "0.92.0" | |
| 6651 | 6651 | dependencies = [ | |
| 6652 | 6652 | "makeover-layout", | |
| 6653 | 6653 | "quasi-router", | |
| @@ -6655,7 +6655,7 @@ | |||
| 6655 | 6655 | ||
| 6656 | 6656 | [[package]] | |
| 6657 | 6657 | name = "quasi-http" | |
| 6658 | - | version = "0.91.1" | |
| 6658 | + | version = "0.92.0" | |
| 6659 | 6659 | dependencies = [ | |
| 6660 | 6660 | "form_urlencoded", | |
| 6661 | 6661 | "http 1.4.2", | |
| @@ -6664,7 +6664,7 @@ | |||
| 6664 | 6664 | ||
| 6665 | 6665 | [[package]] | |
| 6666 | 6666 | name = "quasi-router" | |
| 6667 | - | version = "0.91.1" | |
| 6667 | + | version = "0.92.0" | |
| 6668 | 6668 | dependencies = [ | |
| 6669 | 6669 | "makeover-layout", | |
| 6670 | 6670 | ] | |
| @@ -6687,7 +6687,7 @@ | |||
| 6687 | 6687 | ||
| 6688 | 6688 | [[package]] | |
| 6689 | 6689 | name = "quasi-webview" | |
| 6690 | - | version = "0.91.1" | |
| 6690 | + | version = "0.92.0" | |
| 6691 | 6691 | dependencies = [ | |
| 6692 | 6692 | "docengine", | |
| 6693 | 6693 | "makeover-layout", | |
| @@ -6763,7 +6763,7 @@ | |||
| 6763 | 6763 | "once_cell", | |
| 6764 | 6764 | "socket2", | |
| 6765 | 6765 | "tracing", | |
| 6766 | - | "windows-sys 0.52.0", | |
| 6766 | + | "windows-sys 0.59.0", | |
| 6767 | 6767 | ] | |
| 6768 | 6768 | ||
| 6769 | 6769 | [[package]] | |
| @@ -7326,7 +7326,7 @@ | |||
| 7326 | 7326 | "errno", | |
| 7327 | 7327 | "libc", | |
| 7328 | 7328 | "linux-raw-sys 0.4.15", | |
| 7329 | - | "windows-sys 0.52.0", | |
| 7329 | + | "windows-sys 0.59.0", | |
| 7330 | 7330 | ] | |
| 7331 | 7331 | ||
| 7332 | 7332 | [[package]] | |
| @@ -7339,7 +7339,7 @@ | |||
| 7339 | 7339 | "errno", | |
| 7340 | 7340 | "libc", | |
| 7341 | 7341 | "linux-raw-sys 0.12.1", | |
| 7342 | - | "windows-sys 0.52.0", | |
| 7342 | + | "windows-sys 0.59.0", | |
| 7343 | 7343 | ] | |
| 7344 | 7344 | ||
| 7345 | 7345 | [[package]] | |
| @@ -7419,7 +7419,7 @@ | |||
| 7419 | 7419 | "security-framework 3.7.0", | |
| 7420 | 7420 | "security-framework-sys", | |
| 7421 | 7421 | "webpki-root-certs", | |
| 7422 | - | "windows-sys 0.52.0", | |
| 7422 | + | "windows-sys 0.59.0", | |
| 7423 | 7423 | ] | |
| 7424 | 7424 | ||
| 7425 | 7425 | [[package]] | |
| @@ -8545,7 +8545,7 @@ | |||
| 8545 | 8545 | "getrandom 0.4.3", | |
| 8546 | 8546 | "once_cell", | |
| 8547 | 8547 | "rustix 1.1.4", | |
| 8548 | - | "windows-sys 0.52.0", | |
| 8548 | + | "windows-sys 0.59.0", | |
| 8549 | 8549 | ] | |
| 8550 | 8550 | ||
| 8551 | 8551 | [[package]] | |
| @@ -10759,6 +10759,22 @@ | |||
| 10759 | 10759 | "pkg-config", | |
| 10760 | 10760 | ] | |
| 10761 | 10761 | ||
| 10762 | + | [[patch.unused]] | |
| 10763 | + | name = "quasi-immediate" | |
| 10764 | + | version = "0.92.0" | |
| 10765 | + | ||
| 10766 | + | [[patch.unused]] | |
| 10767 | + | name = "quasi-notifs" | |
| 10768 | + | version = "0.92.0" | |
| 10769 | + | ||
| 10770 | + | [[patch.unused]] | |
| 10771 | + | name = "quasi-store" | |
| 10772 | + | version = "0.1.0" | |
| 10773 | + | ||
| 10774 | + | [[patch.unused]] | |
| 10775 | + | name = "quasi-tauri" | |
| 10776 | + | version = "0.92.0" | |
| 10777 | + | ||
| 10762 | 10778 | [[patch.unused]] | |
| 10763 | 10779 | name = "synckit-client" | |
| 10764 | 10780 | version = "0.10.0" | |
| @@ -10778,19 +10794,3 @@ | |||
| 10778 | 10794 | [[patch.unused]] | |
| 10779 | 10795 | name = "painhours" | |
| 10780 | 10796 | version = "0.1.0" | |
| 10781 | - | ||
| 10782 | - | [[patch.unused]] | |
| 10783 | - | name = "quasi-immediate" | |
| 10784 | - | version = "0.91.1" | |
| 10785 | - | ||
| 10786 | - | [[patch.unused]] | |
| 10787 | - | name = "quasi-notifs" | |
| 10788 | - | version = "0.91.1" | |
| 10789 | - | ||
| 10790 | - | [[patch.unused]] | |
| 10791 | - | name = "quasi-store" | |
| 10792 | - | version = "0.1.0" | |
| 10793 | - | ||
| 10794 | - | [[patch.unused]] | |
| 10795 | - | name = "quasi-tauri" | |
| 10796 | - | version = "0.91.1" |
| @@ -163,7 +163,7 @@ | |||
| 163 | 163 | # through quasi beside its Askama equivalent, so the cost of describing a screen | |
| 164 | 164 | # here is measured rather than argued. Not load-bearing for any shipped route. | |
| 165 | 165 | # See wiki look-wave-2, tier G. | |
| 166 | - | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.91" } | |
| 166 | + | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.92" } | |
| 167 | 167 | # The description vocabulary quasi's screen types are built from. Pinned here | |
| 168 | 168 | # rather than reached through quasi-router's re-export because a described | |
| 169 | 169 | # screen names FieldKind and Tone directly; it has to track what quasi-router | |
| @@ -179,13 +179,13 @@ | |||
| 179 | 179 | # For the request head the per-viewer state factory reads. axum re-exports it, | |
| 180 | 180 | # but the factory's signature is quasi-axum's and names `http::request::Parts`. | |
| 181 | 181 | http = "1.3.1" | |
| 182 | - | quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.91" } | |
| 183 | - | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.91" } | |
| 182 | + | quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.92" } | |
| 183 | + | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.92" } | |
| 184 | 184 | # The first-party widget set. The carousel was proved here against three pages | |
| 185 | 185 | # and then moved there; what is left in `quasi/widgets/carousel.rs` is the | |
| 186 | 186 | # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it | |
| 187 | 187 | # sits above quasi-router rather than inside the description suite. | |
| 188 | - | quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.91" } | |
| 188 | + | quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.92" } | |
| 189 | 189 | ||
| 190 | 190 | # Git source browser | |
| 191 | 191 | gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] } | |
| @@ -277,7 +277,7 @@ | |||
| 277 | 277 | # Written from the crate rather than copied into the repo for the reason the | |
| 278 | 278 | # constants exist: a stale copy in a static directory is a silently dead bulk | |
| 279 | 279 | # bar, and the version has to be the one the emitter agrees with. | |
| 280 | - | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.91" } | |
| 280 | + | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.92" } | |
| 281 | 281 | ||
| 282 | 282 | [dev-dependencies] | |
| 283 | 283 | tower = { version = "0.5.3", features = ["util"] } |
| @@ -142,8 +142,11 @@ | |||
| 142 | 142 | if (i >= entries.length) { | |
| 143 | 143 | document.getElementById('version-upload-progress').classList.add('hidden'); | |
| 144 | 144 | document.getElementById('version-upload-success').classList.remove('hidden'); | |
| 145 | - | var filesBtn = document.getElementById('tab-files'); | |
| 146 | - | if (filesBtn) filesBtn.click(); | |
| 145 | + | // Land back on the files tab, filled. This clicked | |
| 146 | + | // `#tab-files`, the tab button, which the described strip | |
| 147 | + | // (`6b24f2df`) stopped emitting -- so the refresh had been a | |
| 148 | + | // no-op. `?tab=files` is what `item_tabs::shown_at` reads. | |
| 149 | + | window.location.href = '/dashboard/item/' + itemId + '?tab=files'; | |
| 147 | 150 | return; | |
| 148 | 151 | } | |
| 149 | 152 | ||
| @@ -218,39 +221,18 @@ | |||
| 218 | 221 | }); | |
| 219 | 222 | } | |
| 220 | 223 | ||
| 221 | - | // Upload to an existing version. One described field per version that | |
| 222 | - | // has no file yet, each carrying its own address, so pressing the | |
| 223 | - | // button reveals that version's field and the binder in `upload.js` | |
| 224 | - | // does the rest. | |
| 225 | - | document.querySelectorAll('.upload-to-version-btn').forEach(function(btn) { | |
| 226 | - | btn.addEventListener('click', function() { | |
| 227 | - | var panel = document.getElementById('existing-version-upload-' + btn.dataset.versionId); | |
| 228 | - | if (!panel) return; | |
| 229 | - | document.getElementById('new-version-form').classList.add('hidden'); | |
| 230 | - | panel.classList.remove('hidden'); | |
| 231 | - | }); | |
| 232 | - | }); | |
| 233 | - | ||
| 234 | - | document.querySelectorAll('.cancel-existing-upload-btn').forEach(function(btn) { | |
| 235 | - | btn.addEventListener('click', function() { | |
| 236 | - | btn.closest('.existing-version-upload').classList.add('hidden'); | |
| 237 | - | document.getElementById('new-version-form').classList.remove('hidden'); | |
| 238 | - | }); | |
| 239 | - | }); | |
| 240 | - | ||
| 241 | - | document.querySelectorAll('.download-version-btn').forEach(function(btn) { | |
| 242 | - | btn.addEventListener('click', function() { | |
| 243 | - | window.location.href = | |
| 244 | - | '/api/versions/' + btn.dataset.versionId + '/download'; | |
| 245 | - | }); | |
| 246 | - | }); | |
| 247 | - | ||
| 248 | - | // The delete button is described: `crate::quasi::version_delete_act`, | |
| 249 | - | // Shape 3 step 4. Route, row, prompt and tone all live on the act, and | |
| 250 | - | // htmx performs it. The download button above is now a plain navigation | |
| 251 | - | // because its route answers 303 to the presigned URL (`8fc6b1af`); it is | |
| 252 | - | // describable as an `Action::get` whenever the files tab converts, which | |
| 253 | - | // is what taking the JSON away bought. | |
| 224 | + | // Nothing binds the version rows any more. The files tab is described | |
| 225 | + | // (`crate::quasi::item_files`, `138ad5ab`) and each control in a row | |
| 226 | + | // says what it is on the act itself: | |
| 227 | + | // | |
| 228 | + | // Download an `Action::get` at the route that answers 303 to the | |
| 229 | + | // presigned URL (`8fc6b1af`, `9dbe9206`). The five lines | |
| 230 | + | // that navigated by hand are gone with it. | |
| 231 | + | // Delete `crate::quasi::version_delete_act`, since Shape 3 step 4. | |
| 232 | + | // | |
| 233 | + | // The reveal pair went with them: a version with no file now shows its | |
| 234 | + | // upload field rather than a button that unhides one, because a | |
| 235 | + | // described cell holds leaves and acts and cannot name a panel. | |
| 254 | 236 | ||
| 255 | 237 | document.getElementById('cancel-version-upload-btn').addEventListener('click', function() { | |
| 256 | 238 | uploader.cancel(); | |
| @@ -267,9 +249,8 @@ | |||
| 267 | 249 | ||
| 268 | 250 | function resetVersionUpload() { | |
| 269 | 251 | document.getElementById('new-version-form').classList.remove('hidden'); | |
| 270 | - | document.querySelectorAll('.existing-version-upload').forEach(function(panel) { | |
| 271 | - | panel.classList.add('hidden'); | |
| 272 | - | }); | |
| 252 | + | // The per-version upload fields are not hidden any more, so there is | |
| 253 | + | // nothing to re-hide here. See `crate::quasi::item_files`. | |
| 273 | 254 | document.getElementById('version-upload-progress').classList.add('hidden'); | |
| 274 | 255 | document.getElementById('version-upload-success').classList.add('hidden'); | |
| 275 | 256 | document.getElementById('version-upload-error').classList.add('hidden'); |
| @@ -44,7 +44,9 @@ | |||
| 44 | 44 | pub mod discover_typeahead; | |
| 45 | 45 | pub mod embeds; | |
| 46 | 46 | pub mod export_act; | |
| 47 | + | pub mod feeds; | |
| 47 | 48 | pub mod forum_memberships; | |
| 49 | + | pub mod item_files; | |
| 48 | 50 | pub mod item_sales; | |
| 49 | 51 | pub mod item_tabs; | |
| 50 | 52 | pub mod library_contacts; |
| @@ -341,12 +341,12 @@ | |||
| 341 | 341 | label: None, | |
| 342 | 342 | }; | |
| 343 | 343 | ||
| 344 | - | let html = crate::templates::ItemFilesTabTemplate { | |
| 344 | + | let html = crate::templates::ItemVersionUploadTemplate { | |
| 345 | 345 | item: audio_item(None), | |
| 346 | 346 | versions: vec![version], | |
| 347 | 347 | } | |
| 348 | 348 | .render() | |
| 349 | - | .expect("render the files tab"); | |
| 349 | + | .expect("render the uploader"); | |
| 350 | 350 | ||
| 351 | 351 | assert!(html.contains(r#"id="version-files""#), "{html}"); | |
| 352 | 352 | assert!( |
| @@ -15,22 +15,20 @@ | |||
| 15 | 15 | //! but a browser. As an [`Act`] it is one sentence: what it is called, where it | |
| 16 | 16 | //! goes, what it replaces, that it is destructive, and what to ask first. | |
| 17 | 17 | //! | |
| 18 | - | //! # Why the row id is a parameter, and why the row grew one | |
| 18 | + | //! # Why it names what it replaced, and why the spelling changed | |
| 19 | 19 | //! | |
| 20 | 20 | //! `DELETE /api/items/{item}/versions/{version}` is a plain API route the | |
| 21 | 21 | //! description layer does not serve; it answers `200` with an empty body, so it | |
| 22 | 22 | //! cannot name what it changed and the control has to. See | |
| 23 | 23 | //! [`Action::replaces`]. | |
| 24 | 24 | //! | |
| 25 | - | //! The blog pair ([`super::blog_delete_act`]) converted because its templates | |
| 26 | - | //! already gave each row a real id. This one's `<tr>` did not, and the handler | |
| 27 | - | //! it replaces reached the row through `closest('tr')`. So the template grew | |
| 28 | - | //! `id="version-row-{id}"` in the same change. That is worth telling apart from | |
| 29 | - | //! the two rows that are still hand-written: `partials/link_row.html` and | |
| 30 | - | //! `partials/tag.html` target `closest .<class>` on rows whose identity is a | |
| 31 | - | //! CSS class and nothing else, and inventing an id for them would be describing | |
| 32 | - | //! a fact the page does not have. A version row has an id in the database and | |
| 33 | - | //! the template was simply not writing it down. | |
| 25 | + | //! It said `#version-row-{id}`, an id the Askama table grew in the same change | |
| 26 | + | //! that landed this module, because the handler it replaced reached the row | |
| 27 | + | //! through `closest('tr')`. The table is described now (`super::item_files`, | |
| 28 | + | //! `138ad5ab`) and a described row carries `data-row` and no id at all, so an | |
| 29 | + | //! id target would name nothing. [`Action::replacing_enclosing`] is the same | |
| 30 | + | //! sentence in the spelling a described row understands, and it needs no | |
| 31 | + | //! parameter: the act is inside the row it removes. | |
| 34 | 32 | //! | |
| 35 | 33 | //! # The download button beside it is sayable now, and was not when this landed | |
| 36 | 34 | //! | |
| @@ -47,47 +45,49 @@ | |||
| 47 | 45 | //! files tab converts. Nothing blocks it any more. | |
| 48 | 46 | ||
| 49 | 47 | use makeover_layout::Tone; | |
| 48 | + | use quasi_router::Action; | |
| 50 | 49 | use quasi_router::screen::Act; | |
| 51 | - | use quasi_router::{Action, Node}; | |
| 52 | 50 | ||
| 53 | 51 | /// The delete button for one version's row. | |
| 54 | 52 | /// | |
| 55 | - | /// Both ids are needed and they are not the same fact: the route is addressed | |
| 56 | - | /// by item and version together, and the row is named by the version alone. | |
| 53 | + | /// Both ids are needed for the address, which is item and version together. | |
| 54 | + | /// The row it removes is the one it sits in, so nothing has to name it. | |
| 57 | 55 | #[must_use] | |
| 58 | - | pub fn act(item_id: &str, version_id: &str) -> Node { | |
| 59 | - | Node::Act( | |
| 60 | - | Act::new( | |
| 61 | - | "Delete", | |
| 62 | - | Action::delete(format!("/api/items/{item_id}/versions/{version_id}")) | |
| 63 | - | .replacing(format!("version-row-{version_id}")), | |
| 64 | - | ) | |
| 65 | - | .tone(Tone::Danger) | |
| 66 | - | .confirm("Delete this version?"), | |
| 56 | + | pub fn act(item_id: &str, version_id: &str) -> Act { | |
| 57 | + | Act::new( | |
| 58 | + | "Delete", | |
| 59 | + | Action::delete(format!("/api/items/{item_id}/versions/{version_id}")).replacing_enclosing(), | |
| 67 | 60 | ) | |
| 68 | - | } | |
| 69 | - | ||
| 70 | - | /// The same act as a fragment, for the Askama call site. | |
| 71 | - | #[must_use] | |
| 72 | - | pub fn html(item_id: &str, version_id: &str) -> String { | |
| 73 | - | use quasi_axum::Serves as _; | |
| 74 | - | ||
| 75 | - | quasi_webview::Webview::new().fragment(&act(item_id, version_id)) | |
| 61 | + | .tone(Tone::Danger) | |
| 62 | + | .confirm("Delete this version?") | |
| 76 | 63 | } | |
| 77 | 64 | ||
| 78 | 65 | #[cfg(test)] | |
| 79 | 66 | mod tests { | |
| 67 | + | use quasi_router::Node; | |
| 68 | + | ||
| 80 | 69 | /// What the click handler said in four places, said once: where it goes, | |
| 81 | 70 | /// what it replaces, and what to ask first. | |
| 71 | + | /// | |
| 72 | + | /// Rendered through a bare `Node::Act` rather than through the panel: this | |
| 73 | + | /// module owns the sentence, and `super::item_files` asserts it arrives in | |
| 74 | + | /// the row. | |
| 75 | + | fn html() -> String { | |
| 76 | + | use quasi_axum::Serves as _; | |
| 77 | + | ||
| 78 | + | quasi_webview::Webview::new().fragment(&Node::Act(super::act("7", "42"))) | |
| 79 | + | } | |
| 80 | + | ||
| 82 | 81 | #[test] | |
| 83 | 82 | fn a_delete_names_its_route_its_row_and_its_prompt() { | |
| 84 | - | let html = super::html("7", "42"); | |
| 83 | + | let html = html(); | |
| 85 | 84 | ||
| 86 | 85 | assert!( | |
| 87 | 86 | html.contains(r#"hx-delete="/api/items/7/versions/42""#), | |
| 88 | 87 | "{html}" | |
| 89 | 88 | ); | |
| 90 | - | assert!(html.contains(r##"hx-target="#version-row-42""##), "{html}"); | |
| 89 | + | // The row it sits in, rather than an id the table had to grow. | |
| 90 | + | assert!(!html.contains("version-row-42"), "{html}"); | |
| 91 | 91 | assert!( | |
| 92 | 92 | html.contains(r#"hx-confirm="Delete this version?""#), | |
| 93 | 93 | "{html}" | |
| @@ -99,16 +99,14 @@ | |||
| 99 | 99 | /// the stylesheet. | |
| 100 | 100 | #[test] | |
| 101 | 101 | fn a_delete_says_it_is_dangerous_in_a_word_every_renderer_reads() { | |
| 102 | - | let html = super::html("7", "42"); | |
| 103 | - | ||
| 104 | - | assert!(html.contains(r#"data-tone="danger""#), "{html}"); | |
| 102 | + | assert!(html().contains(r#"data-tone="danger""#)); | |
| 105 | 103 | } | |
| 106 | 104 | ||
| 107 | 105 | /// The negative half, as `export_act` asserts it: the dispatcher is gone | |
| 108 | 106 | /// from this control, not merely bypassed. | |
| 109 | 107 | #[test] | |
| 110 | 108 | fn nothing_here_goes_through_the_dispatcher() { | |
| 111 | - | let html = super::html("7", "42"); | |
| 109 | + | let html = html(); | |
| 112 | 110 | ||
| 113 | 111 | assert!(!html.contains("data-action"), "{html}"); | |
| 114 | 112 | assert!(!html.contains("delete-version-btn"), "{html}"); |
| @@ -256,7 +256,6 @@ | |||
| 256 | 256 | CollectionTemplate, | |
| 257 | 257 | // Library tabs | |
| 258 | 258 | LibraryPurchasesTabTemplate, | |
| 259 | - | LibraryFeedTabTemplate, | |
| 260 | 259 | LibraryCollectionsTabTemplate, | |
| 261 | 260 | // Follow button | |
| 262 | 261 | FollowButtonTemplate, | |
| @@ -269,7 +268,7 @@ | |||
| 269 | 268 | ItemOverviewTabTemplate, | |
| 270 | 269 | ItemDetailsTabTemplate, | |
| 271 | 270 | ItemPricingTabTemplate, | |
| 272 | - | ItemFilesTabTemplate, | |
| 271 | + | ItemVersionUploadTemplate, | |
| 273 | 272 | ItemEmbedTabTemplate, | |
| 274 | 273 | // Onboarding checklist | |
| 275 | 274 | OnboardingChecklistPartialTemplate, |
| @@ -593,19 +593,6 @@ | |||
| 593 | 593 | pub wishlists: Vec<crate::db::wishlists::WishlistItem>, | |
| 594 | 594 | } | |
| 595 | 595 | ||
| 596 | - | /// Library feed tab (items from followed users, projects, and tags). | |
| 597 | - | #[derive(Template)] | |
| 598 | - | #[template(path = "partials/tabs/library_feed.html")] | |
| 599 | - | pub struct LibraryFeedTabTemplate { | |
| 600 | - | pub items: Vec<DiscoverItem>, | |
| 601 | - | pub total_items: u32, | |
| 602 | - | pub current_page: u32, | |
| 603 | - | pub total_pages: u32, | |
| 604 | - | pub pagination_range: Vec<u32>, | |
| 605 | - | pub showing_start: u32, | |
| 606 | - | pub showing_end: u32, | |
| 607 | - | } | |
| 608 | - | ||
| 609 | 596 | /// Per-project revenue for the user analytics top projects list. | |
| 610 | 597 | pub struct ProjectRevenue { | |
| 611 | 598 | pub title: String, | |
| @@ -909,10 +896,15 @@ | |||
| 909 | 896 | pub license_preset_options: Vec<(&'static str, &'static str)>, | |
| 910 | 897 | } | |
| 911 | 898 | ||
| 912 | - | /// Item files tab: version upload + download table. | |
| 899 | + | /// The upload machine on the item's Files panel. | |
| 900 | + | /// | |
| 901 | + | /// The bespoke half of a described panel (`crate::quasi::item_files`): the | |
| 902 | + | /// versions table above it is a description, and this is the markup | |
| 903 | + | /// `static/item-upload.js` drives. `versions` is still needed, for the one | |
| 904 | + | /// upload field per version that has no file yet. | |
| 913 | 905 | #[derive(Template)] | |
| 914 | - | #[template(path = "partials/tabs/item_files.html")] | |
| 915 | - | pub struct ItemFilesTabTemplate { | |
| 906 | + | #[template(path = "partials/item_version_upload.html")] | |
| 907 | + | pub struct ItemVersionUploadTemplate { | |
| 916 | 908 | pub item: Item, | |
| 917 | 909 | pub versions: Vec<Version>, | |
| 918 | 910 | } |
| @@ -1,5 +1,4 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | - | {%- import "partials/_ui.html" as ui -%} | |
| 3 | 2 | ||
| 4 | 3 | {% block title %}Feed - Makenotwork{% endblock %} | |
| 5 | 4 | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} feed-page"{% endblock %} | |
| @@ -10,50 +9,9 @@ | |||
| 10 | 9 | <div class="container"> | |
| 11 | 10 | <h1 class="page-title">Your Feed</h1> | |
| 12 | 11 | ||
| 13 | - | {% if items.is_empty() %} | |
| 14 | - | {% call ui::empty_state_with_action("Nothing here yet", "Follow users, projects, or tags to see their items here.", "/discover", "Browse Discover") %}{% endcall %} | |
| 15 | - | {% else %} | |
| 16 | - | <div class="feed-meta">Showing {{ showing_start }}-{{ showing_end }} of {{ total_items }} items</div> | |
| 17 | - | ||
| 18 | - | <div class="table-header"> | |
| 19 | - | <span>Type</span> | |
| 20 | - | <span>Name</span> | |
| 21 | - | <span>Tag</span> | |
| 22 | - | <span class="col-right">Price</span> | |
| 23 | - | <span class="col-right">Date</span> | |
| 24 | - | </div> | |
| 25 | - | <div class="results-table well"> | |
| 26 | - | {% for item in items %} | |
| 27 | - | <a href="/i/{{ item.id }}" class="table-row"> | |
| 28 | - | <span class="badge">{{ item.item_type }}</span> | |
| 29 | - | <div class="item-name-cell"> | |
| 30 | - | <span class="item-name">{{ item.name }}</span> | |
| 31 | - | <span class="item-creator">{{ item.creator }}</span> | |
| 32 | - | </div> | |
| 33 | - | <span>{{ item.primary_tag }}</span> | |
| 34 | - | <span class="col-right">{% if item.is_free %}<span class="badge" data-tone="success">Free</span>{% else %}{{ item.price }}{% endif %}</span> | |
| 35 | - | <span class="col-right">{{ item.date }}</span> | |
| 36 | - | </a> | |
| 37 | - | {% endfor %} | |
| 38 | - | </div> | |
| 39 | - | ||
| 40 | - | {% if total_pages > 1 %} | |
| 41 | - | <div class="pagination"> | |
| 42 | - | {% if current_page > 1 %} | |
| 43 | - | <a href="/feed?page={{ current_page - 1 }}">«</a> | |
| 44 | - | {% endif %} | |
| 45 | - | {% for p in pagination_range %} | |
| 46 | - | {% if *p == current_page %} | |
| 47 | - | <span class="current">{{ p }}</span> | |
| 48 | - | {% else %} | |
| 49 | - | <a href="/feed?page={{ p }}">{{ p }}</a> | |
| 50 | - | {% endif %} | |
| 51 | - | {% endfor %} | |
| 52 | - | {% if current_page < total_pages %} | |
| 53 | - | <a href="/feed?page={{ current_page + 1 }}">»</a> | |
| 54 | - | {% endif %} | |
| 55 | - | </div> | |
| 56 | - | {% endif %} | |
| 57 | - | {% endif %} | |
| 12 | + | {#- The feed itself is described: `crate::quasi::feeds` draws the table, | |
| 13 | + | the empty state and the numbered strip, and the library's Feed panel | |
| 14 | + | draws the same one. What is left here is the public shell. -#} | |
| 15 | + | {{ body|safe }} | |
| 58 | 16 | </div> | |
| 59 | 17 | {% endblock %} |