Signed by hanna SSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
assets/base.css +12 −0 assets/fabrica.js +18 −0 crates/web/src/issues.rs +15 −9 crates/web/src/layout.rs +10 −18 crates/web/src/lib.rs +2 −0 crates/web/src/pages.rs +63 −0 assets/base.css +12 −0 177 white-space : nowrap ; 177 white-space : nowrap ; 178 } 178 } 179 179 180 . label-dropdown-wrap { 181 display : flex ; 182 align-items : center ; 183 flex-wrap : wrap ; 184 gap : 0.4rem ; 185 margin-bottom : 0.5rem ; 186 } 187 . label-dropdown-chips { 188 display : flex ; 189 flex-wrap : wrap ; 190 gap : 0.35rem ; 191 } 180 . label-dropdown { 192 . label-dropdown { 181 display : inline-block ; 193 display : inline-block ; 182 position : relative ; 194 position : relative ;
assets/fabrica.js +18 −0 65 } ) ; 65 } ) ; 66 } ) ; 66 } ) ; 67 67 68 69 document . querySelectorAll ( "[data-label-dropdown]" ) . forEach ( function ( dd ) { 70 var chips = dd . parentNode. querySelector ( "[data-label-chips]" ) ; 71 if ( ! chips ) return ; 72 function sync ( ) { 73 chips . textContent = "" ; 74 dd . querySelectorAll ( 'input[type="checkbox"]:checked' ) . forEach ( function ( cb ) { 75 var chip = document . createElement ( "span" ) ; 76 chip . className = "label-chip" ; 77 chip . style. setProperty ( "--label-color" , cb . getAttribute ( "data-label-color" ) || "#888" ) ; 78 chip . textContent = cb . getAttribute ( "data-label-name" ) || cb . value; 79 chips . appendChild ( chip ) ; 80 } ) ; 81 } 82 dd . addEventListener ( "change" , sync ) ; 83 sync ( ) ; 84 } ) ; 85 68 86 69 87 70 88
crates/web/src/issues.rs +15 −9 504 } 504 } 505 505 506 506 507 507 508 508 fn label_dropdown ( repo_labels : & [ Label ] ) -> Markup { 509 fn label_dropdown ( repo_labels : & [ Label ] ) -> Markup { 509 html ! { 510 html ! { 510 details class="menu label-dropdown" data-menu { 511 div class="label-dropdown-wrap" { 511 summary class="btn" { "Select labels" ( icon ( Icon :: ChevronDown ) ) } 512 details class="menu label-dropdown" data-menu data-label-dropdown { 512 div class="menu-popover label-menu" { 513 summary class="btn" { "Select labels " ( icon ( Icon :: ChevronDown ) ) } 513 @for l in repo_labels { 514 div class="menu-popover label-menu" { 514 label class="checkbox" { 515 @for l in repo_labels { 515 input type ="checkbox" name="label" value=( l. id) ; 516 label class="checkbox" { 516 " " span class="label-swatch" style=( format ! ( "--label-color: {}" , css_color ( & l. color) ) ) { } 517 input type ="checkbox" name="label" value=( l. id) 517 " " ( l. name) 518 data-label-name=( l. name) 519 data-label-color=( css_color ( & l. color) ) ; 520 " " span class="label-swatch" style=( format ! ( "--label-color: {}" , css_color ( & l. color) ) ) { } 521 " " ( l. name) 522 } 518 } 523 } 519 } 524 } 520 } 525 } 526 span class="label-dropdown-chips" data-label-chips { } 521 } 527 } 522 } 528 } 523 } 529 }
crates/web/src/layout.rs +10 −18 84 } 84 } 85 } 85 } 86 86 87 87 88 88 89 89 90 fn create_menu ( chrome : & Chrome ) -> Markup { 90 91 let repo_base = repo_base ( & chrome. path) ; 91 fn create_menu ( _chrome : & Chrome ) -> Markup { 92 html ! { 92 html ! { 93 details class="topbar-create menu" data-menu { 93 details class="topbar-create menu" data-menu { 94 summary class="icon-btn" aria-label="Create new" title="Create new" { 94 summary class="icon-btn" aria-label="Create new" title="Create new" { 98 a class="menu-item" role="menuitem" href="/new" { 98 a class="menu-item" role="menuitem" href="/new" { 99 ( icon ( Icon :: Box ) ) span { "New repository" } 99 ( icon ( Icon :: Box ) ) span { "New repository" } 100 } 100 } 101 @if let Some ( base) = & repo_base { 101 a class="menu-item" role="menuitem" href="/new/issue" { 102 a class="menu-item" role="menuitem" href=( format ! ( "{base}/-/issues/new" ) ) { 102 ( icon ( Icon :: Issue ) ) span { "New issue" } 103 ( icon ( Icon :: Issue ) ) span { "New issue" } 103 } 104 } 104 a class="menu-item" role="menuitem" href="/new/pull" { 105 a class="menu-item" role="menuitem" href=( format ! ( "{base}/-/pulls/new" ) ) { 105 ( icon ( Icon :: GitPull ) ) span { "New pull request" } 106 ( icon ( Icon :: GitPull ) ) span { "New pull request" } 107 } 108 } 106 } 109 } 107 } 110 } 108 } 111 } 109 } 112 } 110 } 113 111 114 115 116 fn repo_base ( path : & str ) -> Option < String > { 117 path. split_once ( "/-/" ) . map ( |( base, _) | base. to_string ( ) ) 118 } 119 120 112 121 fn drawer ( chrome : & Chrome ) -> Markup { 113 fn drawer ( chrome : & Chrome ) -> Markup { 122 html ! { 114 html ! {
crates/web/src/lib.rs +2 −0 138 "/new" , 138 "/new" , 139 get ( pages:: new_repo_form) . post ( pages:: new_repo_submit) , 139 get ( pages:: new_repo_form) . post ( pages:: new_repo_submit) , 140 ) 140 ) 141 . route ( "/new/issue" , get ( pages:: new_issue_chooser) ) 142 . route ( "/new/pull" , get ( pages:: new_pull_chooser) ) 141 . route ( "/explore" , get ( pages:: explore) ) 143 . route ( "/explore" , get ( pages:: explore) ) 142 . route ( "/explore/users" , get ( pages:: explore_users) ) 144 . route ( "/explore/users" , get ( pages:: explore_users) ) 143 . route ( "/login" , get ( pages:: login_form) . post ( pages:: login_submit) ) 145 . route ( "/login" , get ( pages:: login_form) . post ( pages:: login_submit) )
crates/web/src/pages.rs +63 −0 248 } ) 248 } ) 249 } 249 } 250 250 251 252 pub async fn new_issue_chooser ( 253 State ( state) : State < AppState > , 254 RequireUser ( user) : RequireUser , 255 jar : CookieJar , 256 uri : Uri , 257 ) -> AppResult < Response > { 258 let body = repo_chooser ( & state, & user, "issues" , "issue" ) . await ?; 259 let ( jar, chrome) = build_chrome ( & state, jar, Some ( user) , uri. path ( ) . to_string ( ) ) ; 260 Ok ( ( jar, page ( & chrome, "New issue" , body) ) . into_response ( ) ) 261 } 262 263 264 pub async fn new_pull_chooser ( 265 State ( state) : State < AppState > , 266 RequireUser ( user) : RequireUser , 267 jar : CookieJar , 268 uri : Uri , 269 ) -> AppResult < Response > { 270 let body = repo_chooser ( & state, & user, "pulls" , "pull request" ) . await ?; 271 let ( jar, chrome) = build_chrome ( & state, jar, Some ( user) , uri. path ( ) . to_string ( ) ) ; 272 Ok ( ( jar, page ( & chrome, "New pull request" , body) ) . into_response ( ) ) 273 } 274 275 276 277 async fn repo_chooser ( state : & AppState , user : & User , seg : & str , noun : & str ) -> AppResult < Markup > { 278 let repos = state. store. repos_by_owner ( & user. id) . await ?; 279 let usable: Vec < _ > = repos 280 . iter ( ) 281 . filter ( |r| { 282 if seg == "pulls" { 283 r. pulls_enabled 284 } else { 285 r. issues_enabled 286 } 287 } ) 288 . collect ( ) ; 289 Ok ( html ! { 290 div class="new-repo" { 291 h1 { "New " ( noun) } 292 div class="card" { 293 p class="muted field-hint" { "Choose a repository." } 294 @if usable. is_empty ( ) { 295 p class="muted" { "You have no repositories with " ( noun) "s enabled." } 296 } @else { 297 ul class="repo-list" { 298 @for r in & usable { 299 li { 300 a class="repo-row" href={ "/" ( user. username) "/" ( r. path) "/-/" ( seg) "/new" } { 301 span class="repo-row-name" { 302 ( icon ( Icon :: Box ) ) span { ( r. path) } 303 } 304 } 305 } 306 } 307 } 308 } 309 } 310 } 311 } ) 312 } 313 251 314 252 pub async fn new_repo_form ( 315 pub async fn new_repo_form ( 253 State ( state) : State < AppState > , 316 State ( state) : State < AppState > ,