Expand 256 hidden lines 257 257 } 258 258 div class="issue-main" { 259 259 article class="issue-thread" { 260 - ( comment_card ( & author, & issue. body, issue. created_at, None ) ) 260 + @let body_action = format ! ( "/issue/{}/edit-body" , issue. id) ; 261 + ( comment_card ( & author, & issue. body, issue. created_at, 262 + ( can_write || is_author) . then_some ( ( body_action. as_str ( ) , csrf. as_str ( ) ) ) ) ) 261 263 @for ( c, cauthor) in & comment_rows { 262 264 @let editable = can_write || viewer_id. as_deref ( ) == Some ( c. author_id. as_str ( ) ) ; 265 + @let action = format ! ( "/comment/{}/edit" , c. id) ; 263 266 ( comment_card ( cauthor, & c. body, c. created_at, 264 - editable. then_some ( ( c. id. as_str ( ) , csrf. as_str ( ) ) ) ) ) 267 + editable. then_some ( ( action. as_str ( ) , csrf. as_str ( ) ) ) ) ) 265 268 } 266 269 @if issue. locked ( ) { ( locked_note ( can_write) ) } 267 270 @if can_write || ( is_author && !issue. locked ( ) ) { Expand 31 hidden lines 299 302 300 303 301 304 302 - 305 + 306 + 307 + 303 308 pub ( crate ) fn comment_card ( 304 309 author : & str , 305 310 body : & str , Expand 5 hidden lines 311 316 div class="comment-head muted" { 312 317 strong { ( author) } 313 318 span { " commented " ( crate :: activity:: fmt_relative ( created_at, crate :: now_ms ( ) ) ) } 314 - @if let Some ( ( id, csrf) ) = edit { 319 + @if let Some ( ( action, csrf) ) = edit { 315 320 details class="comment-edit" { 316 321 summary { "Edit" } 317 - form method="post" action=( format ! ( "/comment/{id}/edit" ) ) { 322 + form method="post" action=( action) { 318 323 input type ="hidden" name="_csrf" value=( csrf) ; 319 324 textarea name="body" rows="4" required { ( body) } 320 325 button class="btn inline-btn" type ="submit" { "Save" } Expand 90 hidden lines 411 416 } 412 417 } 413 418 414 - 419 + 420 + 415 421 pub ( crate ) fn edit_issue_form ( issue : & Issue , kind : Kind , csrf : & str ) -> Markup { 416 422 html ! { 417 423 details class="issue-edit" { 418 424 summary class="btn" { "Edit" } 419 425 div class="card" { 420 - form method="post" action=( format ! ( "/issue/{}/edit" , issue. id) ) { 426 + form method="post" action=( format ! ( "/issue/{}/edit-title" , issue. id) ) { 421 427 input type ="hidden" name="_csrf" value=( csrf) ; 422 428 label for ="edit-title" { "Title" } 423 429 input type ="text" id="edit-title" name="title" value=( issue. title) required; 424 - label for ="edit-body" { "Description" } 425 - textarea id="edit-body" name="body" rows="6" { ( issue. body) } 426 - button class="btn btn-primary inline-btn" type ="submit" { "Save " ( kind. noun) } 430 + button class="btn btn-primary inline-btn" type ="submit" { "Save " ( kind. noun) " title" } 427 431 } 428 432 } 429 433 } Expand 117 hidden lines 547 551 button class="btn inline-btn" type ="submit" { "Add" } 548 552 } 549 553 } 550 - @if !dependents. is_empty ( ) { 554 + } 555 + @if !dependents. is_empty ( ) { 556 + section { 551 557 h3 { "Blocks" } 552 558 ul class="dep-list" { @for d in dependents { ( row ( d, false ) ) } } 553 559 } Expand 138 hidden lines 692 698 respond_redirect ( result) 693 699 } 694 700 695 - 701 + 696 702 # [ derive ( Debug , Deserialize ) ] 697 - pub struct EditIssueForm { 703 + pub struct EditTitleForm { 698 704 699 705 # [ serde ( default ) ] 700 706 title: String , 701 - 702 - # [ serde ( default ) ] 703 - body: String , 704 707 705 708 # [ serde ( rename = "_csrf" ) ] 706 709 csrf: String , 707 710 } 708 711 709 - 710 - 711 - pub async fn edit ( 712 + 713 + pub async fn edit_title ( 712 714 State ( state) : State < AppState > , 713 715 MaybeUser ( viewer) : MaybeUser , 714 716 jar : CookieJar , 715 717 headers : HeaderMap , 716 718 Path ( id) : Path < String > , 717 - Form ( form) : Form < EditIssueForm > , 719 + Form ( form) : Form < EditTitleForm > , 718 720 ) -> Response { 719 721 if verify_csrf ( & jar, & headers, Some ( & form. csrf) ) . is_err ( ) { 720 722 return AppError :: Forbidden . into_response ( ) ; 721 723 } 722 724 let result = async { 723 - let user = viewer. ok_or ( AppError :: Forbidden ) ?; 724 - let issue = state 725 - . store 726 - . issue_by_id ( & id) 727 - . await ? 728 - . ok_or ( AppError :: NotFound ) ?; 729 - let ( repo, access) = repo_of_issue ( & state, Some ( & user) , & issue) . await ?; 730 - if access < auth:: Access :: Write && user. id != issue. author_id { 731 - return Err ( AppError :: Forbidden ) ; 732 - } 725 + let ( repo, issue) = edit_target ( & state, viewer. as_ref ( ) , & id) . await ?; 733 726 let title = form. title. trim ( ) ; 734 727 if title. is_empty ( ) { 735 728 return Err ( AppError :: BadRequest ( "A title is required." . to_string ( ) ) ) ; 736 729 } 737 730 state 738 731 . store 739 - . update_issue ( & issue. id, title, form. body. trim ( ) ) 732 + . update_issue ( & issue. id, title, & issue. body) 740 733 . await ?; 741 734 Ok :: < String , AppError > ( issue_url ( & state, & repo, & issue) . await ) 742 735 } Expand 1 hidden lines 744 737 respond_redirect ( result) 745 738 } 746 739 740 + 741 + 742 + pub async fn edit_body ( 743 + State ( state) : State < AppState > , 744 + MaybeUser ( viewer) : MaybeUser , 745 + jar : CookieJar , 746 + headers : HeaderMap , 747 + Path ( id) : Path < String > , 748 + Form ( form) : Form < CommentForm > , 749 + ) -> Response { 750 + if verify_csrf ( & jar, & headers, Some ( & form. csrf) ) . is_err ( ) { 751 + return AppError :: Forbidden . into_response ( ) ; 752 + } 753 + let result = async { 754 + let ( repo, issue) = edit_target ( & state, viewer. as_ref ( ) , & id) . await ?; 755 + state 756 + . store 757 + . update_issue ( & issue. id, & issue. title, form. body. trim ( ) ) 758 + . await ?; 759 + Ok :: < String , AppError > ( issue_url ( & state, & repo, & issue) . await ) 760 + } 761 + . await ; 762 + respond_redirect ( result) 763 + } 764 + 765 + 766 + async fn edit_target ( 767 + state : & AppState , 768 + viewer : Option < & User > , 769 + id : & str , 770 + ) -> AppResult < ( model:: Repo , Issue ) > { 771 + let user = viewer. ok_or ( AppError :: Forbidden ) ?; 772 + let issue = state 773 + . store 774 + . issue_by_id ( id) 775 + . await ? 776 + . ok_or ( AppError :: NotFound ) ?; 777 + let ( repo, access) = repo_of_issue ( state, Some ( user) , & issue) . await ?; 778 + if access < auth:: Access :: Write && user. id != issue. author_id { 779 + return Err ( AppError :: Forbidden ) ; 780 + } 781 + Ok ( ( repo, issue) ) 782 + } 783 + 747 784 748 785 pub async fn comment_edit ( 749 786 State ( state) : State < AppState > ,