Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
assets/base.css +31 −1
| @@ -742,7 +742,8 @@ body.drawer-open .drawer-backdrop { | |||
| 742 | 742 | margin-bottom: 0; | |
| 743 | 743 | } | |
| 744 | 744 | ||
| 745 | - | /* Collaborators: a bordered list inside the card, then an add row. */ | |
| 745 | + | /* Collaborators: a bordered list inside the card, then an add row. Each row is | |
| 746 | + | [avatar] [name] [role] on the left, remove button on the right. */ | |
| 746 | 747 | .collab-list { | |
| 747 | 748 | margin-bottom: 1rem; | |
| 748 | 749 | } | |
| @@ -750,6 +751,28 @@ body.drawer-open .drawer-backdrop { | |||
| 750 | 751 | padding-left: 0; | |
| 751 | 752 | padding-right: 0; | |
| 752 | 753 | } | |
| 754 | + | /* The first row abuts the card padding; drop its own top padding so the list | |
| 755 | + | doesn't sit with a doubled gap at the top of the card. */ | |
| 756 | + | .collab-list .entry-row:first-child { | |
| 757 | + | padding-top: 0; | |
| 758 | + | } | |
| 759 | + | .collab-ident { | |
| 760 | + | display: flex; | |
| 761 | + | align-items: center; | |
| 762 | + | gap: 0.6rem; | |
| 763 | + | min-width: 0; | |
| 764 | + | } | |
| 765 | + | .collab-ident .entry-row-title { | |
| 766 | + | display: inline; | |
| 767 | + | } | |
| 768 | + | .collab-perm { | |
| 769 | + | font-size: 0.8em; | |
| 770 | + | color: var(--fb-fg-muted); | |
| 771 | + | border: 1px solid var(--fb-border); | |
| 772 | + | border-radius: 999px; | |
| 773 | + | padding: 0.05rem 0.55rem; | |
| 774 | + | text-transform: capitalize; | |
| 775 | + | } | |
| 753 | 776 | .collab-list .entry-row-actions .btn { | |
| 754 | 777 | padding: 0.25rem 0.6rem; | |
| 755 | 778 | font-size: 0.85em; | |
| @@ -1192,6 +1215,13 @@ label { | |||
| 1192 | 1215 | margin: 0.75rem 0 0.25rem; | |
| 1193 | 1216 | font-weight: 600; | |
| 1194 | 1217 | } | |
| 1218 | + | /* The first label in a card form abuts the card padding — drop its top margin so | |
| 1219 | + | the form doesn't sit with a doubled gap at the top. The CSRF hidden input is | |
| 1220 | + | usually the real first child, so also match the label right after it. */ | |
| 1221 | + | .card form > label:first-child, | |
| 1222 | + | .card form > input[type="hidden"]:first-child + label { | |
| 1223 | + | margin-top: 0; | |
| 1224 | + | } | |
| 1195 | 1225 | input[type="text"], | |
| 1196 | 1226 | input[type="password"], | |
| 1197 | 1227 | input[type="email"], | |
crates/web/src/groups.rs +5 −49
| @@ -287,7 +287,11 @@ pub async fn settings_view( | |||
| 287 | 287 | } | |
| 288 | 288 | } | |
| 289 | 289 | } | |
| 290 | - | (group_collaborators_section(gid, &chrome.csrf, &collaborators)) | |
| 290 | + | section class="listing" { | |
| 291 | + | h2 { "Collaborators" } | |
| 292 | + | p class="muted field-hint" { "Group collaborators gain the same access to every repository within this group." } | |
| 293 | + | (crate::repo::collaborators_card(&format!("/group-settings/{gid}"), &chrome.csrf, &collaborators)) | |
| 294 | + | } | |
| 291 | 295 | section class="listing" { | |
| 292 | 296 | h2 { "Danger zone" } | |
| 293 | 297 | div class="card danger-zone" { | |
| @@ -312,54 +316,6 @@ pub async fn settings_view( | |||
| 312 | 316 | result.unwrap_or_else(IntoResponse::into_response) | |
| 313 | 317 | } | |
| 314 | 318 | ||
| 315 | - | /// The group collaborators section: current members with remove buttons, add form. | |
| 316 | - | fn group_collaborators_section( | |
| 317 | - | id: &str, | |
| 318 | - | csrf: &str, | |
| 319 | - | collaborators: &[store::Collaborator], | |
| 320 | - | ) -> Markup { | |
| 321 | - | let perm_option = |value: &str, label: &str| html! { option value=(value) { (label) } }; | |
| 322 | - | html! { | |
| 323 | - | section class="listing" { | |
| 324 | - | h2 { "Collaborators" } | |
| 325 | - | p class="muted field-hint" { "Group collaborators gain the same access to every repository within this group." } | |
| 326 | - | div class="card" { | |
| 327 | - | @if collaborators.is_empty() { | |
| 328 | - | p class="muted" { "No collaborators yet." } | |
| 329 | - | } @else { | |
| 330 | - | ul class="entry-list collab-list" { | |
| 331 | - | @for c in collaborators { | |
| 332 | - | li class="entry-row" { | |
| 333 | - | div class="entry-row-body" { | |
| 334 | - | a class="entry-row-title" href={ "/" (c.username) } { (c.username) } | |
| 335 | - | div class="entry-row-meta muted" { (c.permission) } | |
| 336 | - | } | |
| 337 | - | div class="entry-row-actions" { | |
| 338 | - | form method="post" action=(format!("/group-settings/{id}/collaborators/remove")) { | |
| 339 | - | input type="hidden" name="_csrf" value=(csrf); | |
| 340 | - | input type="hidden" name="user_id" value=(c.user_id); | |
| 341 | - | button class="btn btn-danger" type="submit" { "Remove" } | |
| 342 | - | } | |
| 343 | - | } | |
| 344 | - | } | |
| 345 | - | } | |
| 346 | - | } | |
| 347 | - | } | |
| 348 | - | form class="collab-add" method="post" action=(format!("/group-settings/{id}/collaborators")) { | |
| 349 | - | input type="hidden" name="_csrf" value=(csrf); | |
| 350 | - | input type="text" name="username" placeholder="Username" aria-label="Username" required; | |
| 351 | - | select name="permission" aria-label="Permission" { | |
| 352 | - | (perm_option("read", "Read")) | |
| 353 | - | (perm_option("write", "Write")) | |
| 354 | - | (perm_option("admin", "Admin")) | |
| 355 | - | } | |
| 356 | - | button class="btn btn-primary inline-btn" type="submit" { "Add" } | |
| 357 | - | } | |
| 358 | - | } | |
| 359 | - | } | |
| 360 | - | } | |
| 361 | - | } | |
| 362 | - | ||
| 363 | 319 | /// The group general-settings form (rename + visibility). | |
| 364 | 320 | #[derive(Debug, Deserialize)] | |
| 365 | 321 | pub struct GroupGeneralForm { | |
crates/web/src/repo.rs +41 −29
| @@ -1504,44 +1504,56 @@ fn fmt_duration(secs: i64) -> String { | |||
| 1504 | 1504 | /// The Collaborators section of the repo settings page: the current list with | |
| 1505 | 1505 | /// remove buttons, and the add form. | |
| 1506 | 1506 | fn collaborators_section(id: &str, csrf: &str, collaborators: &[store::Collaborator]) -> Markup { | |
| 1507 | - | let perm_option = |value: &str, label: &str| html! { option value=(value) { (label) } }; | |
| 1508 | 1507 | html! { | |
| 1509 | 1508 | section class="listing" { | |
| 1510 | 1509 | h2 { "Collaborators" } | |
| 1511 | - | div class="card" { | |
| 1512 | - | @if collaborators.is_empty() { | |
| 1513 | - | p class="muted" { "No collaborators yet." } | |
| 1514 | - | } @else { | |
| 1515 | - | ul class="entry-list collab-list" { | |
| 1516 | - | @for c in collaborators { | |
| 1517 | - | li class="entry-row" { | |
| 1518 | - | div class="entry-row-body" { | |
| 1519 | - | a class="entry-row-title" href={ "/" (c.username) } { (c.username) } | |
| 1520 | - | div class="entry-row-meta muted" { (c.permission) } | |
| 1521 | - | } | |
| 1522 | - | div class="entry-row-actions" { | |
| 1523 | - | form method="post" | |
| 1524 | - | action=(format!("/repo-settings/{id}/collaborators/remove")) { | |
| 1525 | - | input type="hidden" name="_csrf" value=(csrf); | |
| 1526 | - | input type="hidden" name="user_id" value=(c.user_id); | |
| 1527 | - | button class="btn btn-danger" type="submit" { "Remove" } | |
| 1528 | - | } | |
| 1510 | + | (collaborators_card(&format!("/repo-settings/{id}"), csrf, collaborators)) | |
| 1511 | + | } | |
| 1512 | + | } | |
| 1513 | + | } | |
| 1514 | + | ||
| 1515 | + | /// The collaborators card — an avatar/name/role list with remove buttons, then | |
| 1516 | + | /// an add row. Shared by the repo and group settings pages; `base` is the action | |
| 1517 | + | /// URL prefix (`/repo-settings/{id}` or `/group-settings/{id}`). | |
| 1518 | + | pub(crate) fn collaborators_card( | |
| 1519 | + | base: &str, | |
| 1520 | + | csrf: &str, | |
| 1521 | + | collaborators: &[store::Collaborator], | |
| 1522 | + | ) -> Markup { | |
| 1523 | + | let perm_option = |value: &str, label: &str| html! { option value=(value) { (label) } }; | |
| 1524 | + | html! { | |
| 1525 | + | div class="card" { | |
| 1526 | + | @if collaborators.is_empty() { | |
| 1527 | + | p class="muted" { "No collaborators yet." } | |
| 1528 | + | } @else { | |
| 1529 | + | ul class="entry-list collab-list" { | |
| 1530 | + | @for c in collaborators { | |
| 1531 | + | li class="entry-row" { | |
| 1532 | + | div class="collab-ident" { | |
| 1533 | + | img class="avatar avatar-sm" src=(format!("/avatar/{}", c.username)) alt=""; | |
| 1534 | + | a class="entry-row-title" href={ "/" (c.username) } { (c.username) } | |
| 1535 | + | span class="collab-perm" { (c.permission) } | |
| 1536 | + | } | |
| 1537 | + | div class="entry-row-actions" { | |
| 1538 | + | form method="post" action=(format!("{base}/collaborators/remove")) { | |
| 1539 | + | input type="hidden" name="_csrf" value=(csrf); | |
| 1540 | + | input type="hidden" name="user_id" value=(c.user_id); | |
| 1541 | + | button class="btn btn-danger" type="submit" { "Remove" } | |
| 1529 | 1542 | } | |
| 1530 | 1543 | } | |
| 1531 | 1544 | } | |
| 1532 | 1545 | } | |
| 1533 | 1546 | } | |
| 1534 | - | form class="collab-add" method="post" | |
| 1535 | - | action=(format!("/repo-settings/{id}/collaborators")) { | |
| 1536 | - | input type="hidden" name="_csrf" value=(csrf); | |
| 1537 | - | input type="text" name="username" placeholder="Username" aria-label="Username" required; | |
| 1538 | - | select name="permission" aria-label="Permission" { | |
| 1539 | - | (perm_option("read", "Read")) | |
| 1540 | - | (perm_option("write", "Write")) | |
| 1541 | - | (perm_option("admin", "Admin")) | |
| 1542 | - | } | |
| 1543 | - | button class="btn btn-primary inline-btn" type="submit" { "Add" } | |
| 1547 | + | } | |
| 1548 | + | form class="collab-add" method="post" action=(format!("{base}/collaborators")) { | |
| 1549 | + | input type="hidden" name="_csrf" value=(csrf); | |
| 1550 | + | input type="text" name="username" placeholder="Username" aria-label="Username" required; | |
| 1551 | + | select name="permission" aria-label="Permission" { | |
| 1552 | + | (perm_option("read", "Read")) | |
| 1553 | + | (perm_option("write", "Write")) | |
| 1554 | + | (perm_option("admin", "Admin")) | |
| 1544 | 1555 | } | |
| 1556 | + | button class="btn btn-primary inline-btn" type="submit" { "Add" } | |
| 1545 | 1557 | } | |
| 1546 | 1558 | } | |
| 1547 | 1559 | } | |
data/fabrica/trash/01kydt075vpteqnjzyms8v3vtm-1785061358405.git/HEAD +0 −0
No textual changes.
data/fabrica/trash/01kydt075vpteqnjzyms8v3vtm-1785061358405.git/config +0 −0
No textual changes.
data/fabrica/trash/01kydt075vpteqnjzyms8v3vtm-1785061358405.git/description +0 −0
No textual changes.
data/fabrica/trash/01kydt075vpteqnjzyms8v3vtm-1785061358405.git/hooks/README.sample +0 −0
No textual changes.
data/fabrica/trash/01kydt075vpteqnjzyms8v3vtm-1785061358405.git/hooks/post-receive +0 −0
No textual changes.
data/fabrica/trash/01kydt075vpteqnjzyms8v3vtm-1785061358405.git/hooks/pre-receive +0 −0
No textual changes.
data/fabrica/trash/01kydt075vpteqnjzyms8v3vtm-1785061358405.git/info/exclude +0 −0
No textual changes.
data/fabrica/trash/01kydta3a2b6gmfh0c4x7tx4b0-1785061363714.git/HEAD +0 −0
No textual changes.
data/fabrica/trash/01kydta3a2b6gmfh0c4x7tx4b0-1785061363714.git/config +0 −0
No textual changes.
data/fabrica/trash/01kydta3a2b6gmfh0c4x7tx4b0-1785061363714.git/description +0 −0
No textual changes.
data/fabrica/trash/01kydta3a2b6gmfh0c4x7tx4b0-1785061363714.git/hooks/README.sample +0 −0
No textual changes.
data/fabrica/trash/01kydta3a2b6gmfh0c4x7tx4b0-1785061363714.git/hooks/post-receive +0 −0
No textual changes.
data/fabrica/trash/01kydta3a2b6gmfh0c4x7tx4b0-1785061363714.git/hooks/pre-receive +0 −0
No textual changes.
data/fabrica/trash/01kydta3a2b6gmfh0c4x7tx4b0-1785061363714.git/info/exclude +0 −0
No textual changes.
data/fabrica/trash/01kydtah34meqbxcfatt6m90p3-1785061376374.git/HEAD +0 −0
No textual changes.
data/fabrica/trash/01kydtah34meqbxcfatt6m90p3-1785061376374.git/config +0 −0
No textual changes.
data/fabrica/trash/01kydtah34meqbxcfatt6m90p3-1785061376374.git/description +0 −0
No textual changes.
data/fabrica/trash/01kydtah34meqbxcfatt6m90p3-1785061376374.git/hooks/README.sample +0 −0
No textual changes.
data/fabrica/trash/01kydtah34meqbxcfatt6m90p3-1785061376374.git/hooks/post-receive +0 −0
No textual changes.
data/fabrica/trash/01kydtah34meqbxcfatt6m90p3-1785061376374.git/hooks/pre-receive +0 −0
No textual changes.
data/fabrica/trash/01kydtah34meqbxcfatt6m90p3-1785061376374.git/info/exclude +0 −0
No textual changes.