Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
assets/base.css +51 −14
| @@ -516,9 +516,13 @@ body.drawer-open .drawer-backdrop { | |||
| 516 | } | 516 | } |
| 517 | .issue-title-line { | 517 | .issue-title-line { |
| 518 | display: flex; | 518 | display: flex; |
| 519 | align-items: flex-start; | 519 | align-items: center; |
| 520 | justify-content: space-between; | 520 | justify-content: space-between; |
| 521 | gap: 1rem; | 521 | gap: 0.5rem; |
| 522 | } | ||
| 523 | /* Hide the title while its editor is open (GitHub-style inline rename). */ | ||
| 524 | .issue-title-line:has(.issue-edit[open]) h1 { | ||
| 525 | display: none; | ||
| 522 | } | 526 | } |
| 523 | .issue-edit, | 527 | .issue-edit, |
| 524 | .comment-edit { | 528 | .comment-edit { |
| @@ -533,30 +537,63 @@ body.drawer-open .drawer-backdrop { | |||
| 533 | .comment-edit > summary::-webkit-details-marker { | 537 | .comment-edit > summary::-webkit-details-marker { |
| 534 | display: none; | 538 | display: none; |
| 535 | } | 539 | } |
| 536 | .issue-edit .card { | 540 | /* Edit / Cancel label swap on the toggle. */ |
| 537 | margin-top: 0.5rem; | 541 | .issue-edit:not([open]) .edit-close, |
| 538 | width: min(32rem, 90vw); | 542 | .comment-edit:not([open]) .edit-close { |
| 543 | display: none; | ||
| 544 | } | ||
| 545 | .issue-edit[open] .edit-open, | ||
| 546 | .comment-edit[open] .edit-open { | ||
| 547 | display: none; | ||
| 548 | } | ||
| 549 | /* Title editor: a full-width input with Cancel (the toggle) and Save beside it. */ | ||
| 550 | .issue-edit[open] { | ||
| 551 | display: flex; | ||
| 552 | align-items: center; | ||
| 553 | gap: 0.5rem; | ||
| 554 | flex: 1 1 100%; | ||
| 555 | } | ||
| 556 | .issue-edit[open] > summary { | ||
| 557 | order: 2; | ||
| 558 | } | ||
| 559 | .title-edit-form { | ||
| 560 | order: 1; | ||
| 561 | flex: 1; | ||
| 562 | } | ||
| 563 | .title-edit-form input[type="text"] { | ||
| 564 | width: 100%; | ||
| 539 | } | 565 | } |
| 566 | .title-save { | ||
| 567 | order: 3; | ||
| 568 | } | ||
| 569 | /* Comment editor: the toggle sits in the header; when open the rendered body is | ||
| 570 | replaced by the textarea. */ | ||
| 540 | .comment-edit { | 571 | .comment-edit { |
| 541 | margin-left: auto; | 572 | margin-left: auto; |
| 542 | } | 573 | } |
| 543 | /* When opened, break onto its own full-width row so the form isn't cramped. */ | ||
| 544 | .comment-edit[open] { | ||
| 545 | flex-basis: 100%; | ||
| 546 | margin-left: 0; | ||
| 547 | margin-top: 0.4rem; | ||
| 548 | } | ||
| 549 | .comment-edit > summary { | 574 | .comment-edit > summary { |
| 550 | font-size: 0.9em; | 575 | font-size: 0.9em; |
| 551 | color: var(--fb-accent); | 576 | color: var(--fb-accent); |
| 552 | } | 577 | } |
| 553 | .comment-edit form { | 578 | .comment-editor { |
| 554 | margin-top: 0.5rem; | 579 | display: none; |
| 580 | padding: 0.85rem; | ||
| 555 | } | 581 | } |
| 556 | .comment-edit textarea { | 582 | .comment:has(.comment-edit[open]) .comment-editor { |
| 583 | display: block; | ||
| 584 | } | ||
| 585 | .comment:has(.comment-edit[open]) .comment-body { | ||
| 586 | display: none; | ||
| 587 | } | ||
| 588 | .comment-editor textarea { | ||
| 557 | width: 100%; | 589 | width: 100%; |
| 558 | font-family: inherit; | 590 | font-family: inherit; |
| 559 | } | 591 | } |
| 592 | .comment-editor-actions { | ||
| 593 | display: flex; | ||
| 594 | justify-content: flex-end; | ||
| 595 | margin-top: 0.5rem; | ||
| 596 | } | ||
| 560 | .issue-sub { | 597 | .issue-sub { |
| 561 | display: flex; | 598 | display: flex; |
| 562 | align-items: center; | 599 | align-items: center; |
crates/web/src/issues.rs +30 −18
| @@ -247,7 +247,7 @@ pub(crate) async fn view( | |||
| 247 | div class="issue-title-line" { | 247 | div class="issue-title-line" { |
| 248 | h1 { (issue.title) " " span class="muted" { "#" (issue.number) } } | 248 | h1 { (issue.title) " " span class="muted" { "#" (issue.number) } } |
| 249 | @if can_write || is_author { | 249 | @if can_write || is_author { |
| 250 | (edit_issue_form(&issue, kind, &csrf)) | 250 | (edit_issue_form(&issue, &csrf)) |
| 251 | } | 251 | } |
| 252 | } | 252 | } |
| 253 | div class="issue-sub" { | 253 | div class="issue-sub" { |
| @@ -316,18 +316,27 @@ pub(crate) fn comment_card( | |||
| 316 | div class="comment-head muted" { | 316 | div class="comment-head muted" { |
| 317 | strong { (author) } | 317 | strong { (author) } |
| 318 | span { " commented " (crate::activity::fmt_relative(created_at, crate::now_ms())) } | 318 | span { " commented " (crate::activity::fmt_relative(created_at, crate::now_ms())) } |
| 319 | @if let Some((action, csrf)) = edit { | 319 | // A bare toggle; CSS `:has(.comment-edit[open])` swaps the rendered |
| 320 | // body for the editor below, so no JavaScript is needed. | ||
| 321 | @if edit.is_some() { | ||
| 320 | details class="comment-edit" { | 322 | details class="comment-edit" { |
| 321 | summary { "Edit" } | 323 | summary { |
| 322 | form method="post" action=(action) { | 324 | span class="edit-open" { "Edit" } |
| 323 | input type="hidden" name="_csrf" value=(csrf); | 325 | span class="edit-close" { "Cancel" } |
| 324 | textarea name="body" rows="4" required { (body) } | ||
| 325 | button class="btn inline-btn" type="submit" { "Save" } | ||
| 326 | } | 326 | } |
| 327 | } | 327 | } |
| 328 | } | 328 | } |
| 329 | } | 329 | } |
| 330 | div class="comment-body markdown" { (markdown::render(body)) } | 330 | div class="comment-body markdown" { (markdown::render(body)) } |
| 331 | @if let Some((action, csrf)) = edit { | ||
| 332 | form class="comment-editor" method="post" action=(action) { | ||
| 333 | input type="hidden" name="_csrf" value=(csrf); | ||
| 334 | textarea name="body" rows="6" required { (body) } | ||
| 335 | div class="comment-editor-actions" { | ||
| 336 | button class="btn btn-primary inline-btn" type="submit" { "Save" } | ||
| 337 | } | ||
| 338 | } | ||
| 339 | } | ||
| 331 | } | 340 | } |
| 332 | } | 341 | } |
| 333 | } | 342 | } |
| @@ -416,20 +425,23 @@ pub(crate) fn lock_button(issue: &Issue, csrf: &str) -> Markup { | |||
| 416 | } | 425 | } |
| 417 | } | 426 | } |
| 418 | 427 | ||
| 419 | /// The edit-title form, revealed by a toggle in the issue/PR header. (The | 428 | /// The edit-title toggle in the issue/PR header. When open it hides the title and |
| 420 | /// description is edited from its own comment card.) | 429 | /// shows a full-width single-line input with Cancel (the toggle) and Save beside |
| 421 | pub(crate) fn edit_issue_form(issue: &Issue, kind: Kind, csrf: &str) -> Markup { | 430 | /// it, GitHub-style. (The description is edited from its own comment card.) |
| 431 | pub(crate) fn edit_issue_form(issue: &Issue, csrf: &str) -> Markup { | ||
| 432 | let form_id = format!("title-edit-{}", issue.id); | ||
| 422 | html! { | 433 | html! { |
| 423 | details class="issue-edit" { | 434 | details class="issue-edit" { |
| 424 | summary class="btn" { "Edit" } | 435 | summary class="btn edit-toggle" { |
| 425 | div class="card" { | 436 | span class="edit-open" { "Edit" } |
| 426 | form method="post" action=(format!("/issue/{}/edit-title", issue.id)) { | 437 | span class="edit-close" { "Cancel" } |
| 427 | input type="hidden" name="_csrf" value=(csrf); | 438 | } |
| 428 | label for="edit-title" { "Title" } | 439 | form id=(form_id) class="title-edit-form" method="post" |
| 429 | input type="text" id="edit-title" name="title" value=(issue.title) required; | 440 | action=(format!("/issue/{}/edit-title", issue.id)) { |
| 430 | button class="btn btn-primary inline-btn" type="submit" { "Save " (kind.noun) " title" } | 441 | input type="hidden" name="_csrf" value=(csrf); |
| 431 | } | 442 | input type="text" name="title" value=(issue.title) aria-label="Title" required; |
| 432 | } | 443 | } |
| 444 | button class="btn btn-primary title-save" type="submit" form=(form_id) { "Save" } | ||
| 433 | } | 445 | } |
| 434 | } | 446 | } |
| 435 | } | 447 | } |
crates/web/src/pulls.rs +1 −1
| @@ -142,7 +142,7 @@ pub(crate) async fn view( | |||
| 142 | div class="issue-title-line" { | 142 | div class="issue-title-line" { |
| 143 | h1 { (issue.title) " " span class="muted" { "#" (issue.number) } } | 143 | h1 { (issue.title) " " span class="muted" { "#" (issue.number) } } |
| 144 | @if can_write || is_author { | 144 | @if can_write || is_author { |
| 145 | (edit_issue_form(&issue, Kind::pulls(), &csrf)) | 145 | (edit_issue_form(&issue, &csrf)) |
| 146 | } | 146 | } |
| 147 | } | 147 | } |
| 148 | div class="issue-sub" { | 148 | div class="issue-sub" { |