Expand 256 hidden lines 257 257 let ( kind, rest) = view. split_once ( '/' ) . unwrap_or ( ( view, "" ) ) ; 258 258 match kind { 259 259 "tree" => tree_view ( state, viewer, jar, uri, ctx, rest) . await , 260 - "blob" => blob_view ( state, viewer, jar, uri, ctx, rest, false ) . await , 260 + "blob" => blob_view ( state, viewer, jar, uri, ctx, rest) . await , 261 261 "raw" => raw_view ( state, ctx, rest) . await , 262 262 "commits" => commits_view ( state, viewer, jar, uri, ctx, rest) . await , 263 263 "commit" => match rest. split_once ( '/' ) { Expand 58 hidden lines 322 322 } ) 323 323 . await ?; 324 324 let readme = load_readme ( state, & ctx. repo. id, & rev, & entries) . await ; 325 + let base = format ! ( "/{}/{}" , ctx. owner. username, ctx. repo. path) ; 325 326 html ! { 326 327 ( repo_header ( & ctx, Tab :: Code , & rev_name) ) 327 328 ( clone_widget ( state, & ctx) ) 329 + form class="search-row repo-codesearch" method="get" action=( format ! ( "{base}/-/search" ) ) { 330 + input type ="search" name="q" placeholder="Search this repository…" 331 + aria-label="Search this repository" ; 332 + button class="btn" type ="submit" { ( icon ( Icon :: Search ) ) span { "Search" } } 333 + } 328 334 ( tree_table ( & ctx. owner. username, & ctx. repo. path, & rev_name, "" , & entries) ) 329 335 @if let Some ( rendered) = readme { 330 336 div class="card markdown readme" { ( rendered) } Expand 91 hidden lines 422 428 uri : & Uri , 423 429 ctx : RepoCtx , 424 430 rest : & str , 425 - _raw : bool , 426 431 ) -> AppResult < Response > { 432 + 433 + let source_view = uri. query ( ) . is_some_and ( |q| q. contains ( "display=source" ) ) ; 427 434 let ( rev_name, path) = split_ref_path ( rest) ; 428 435 let rev_name = ref_or_default ( & ctx. repo, & rev_name) ; 429 436 if path. is_empty ( ) { Expand 18 hidden lines 448 455 } ) 449 456 . await ?; 450 457 451 - let content_body = render_blob ( & ctx, & rev_name, & path, & blob, attr_lang. as_deref ( ) ) ; 458 + let content_body = render_blob ( 459 + & ctx, 460 + & rev_name, 461 + & path, 462 + & blob, 463 + attr_lang. as_deref ( ) , 464 + source_view, 465 + ) ; 452 466 let body = html ! { 453 467 ( repo_header ( & ctx, Tab :: Code , & rev_name) ) 454 468 ( breadcrumb ( & ctx. owner. username, & ctx. repo. path, & rev_name, & path) ) Expand 4 hidden lines 459 473 Ok ( ( jar, page ( & chrome, & title, body) ) . into_response ( ) ) 460 474 } 461 475 462 - 463 - 476 + 477 + 464 478 fn render_blob ( 465 479 ctx : & RepoCtx , 466 480 rev_name : & str , 467 481 path : & str , 468 482 blob : & git:: Blob , 469 483 attr_lang : Option < & str > , 484 + source_view : bool , 470 485 ) -> Markup { 471 - let raw_url = format ! ( 472 - "/{}/{}/-/raw/{rev_name}/{path}" , 473 - ctx. owner. username, ctx. repo. path 474 - ) ; 486 + let base = format ! ( "/{}/{}" , ctx. owner. username, ctx. repo. path) ; 487 + let raw_url = format ! ( "{base}/-/raw/{rev_name}/{path}" ) ; 488 + let blob_url = format ! ( "{base}/-/blob/{rev_name}/{path}" ) ; 475 489 let filename = path. rsplit ( '/' ) . next ( ) . unwrap_or ( path) ; 490 + let previewable = is_markdown ( filename) && !blob. is_binary; 491 + let show_preview = previewable && !source_view; 476 492 477 493 html ! { 478 494 div class="blob-header" { 479 495 span class="muted" { ( blob. size) " bytes" } 480 - span { a class="btn" href=( raw_url) { "Raw" } 481 - button class="btn" type ="button" data-clipboard=( raw_url) { 482 - ( icon ( Icon :: Copy ) ) span { "Copy path" } 483 - } } 496 + div class="blob-actions" { 497 + @if previewable { 498 + a class=( toggle_class ( show_preview) ) href=( blob_url. clone ( ) ) { "Preview" } 499 + a class=( toggle_class ( !show_preview) ) href=( format ! ( "{blob_url}?display=source" ) ) { "Code" } 500 + } 501 + a class="btn" href=( raw_url) { "Raw" } 502 + button class="btn" type ="button" data-clipboard=( raw_url) { 503 + ( icon ( Icon :: Copy ) ) span { "Copy path" } 504 + } 505 + } 484 506 } 485 507 @if is_image ( filename) { 486 508 p { img src=( raw_url) alt=( filename) style="max-width:100%" ; } Expand 2 hidden lines 489 511 p class="muted" { "Binary file not shown." } 490 512 p { a class="btn" href=( raw_url) { "Download" } } 491 513 } 514 + } @else if show_preview { 515 + div class="card markdown" { 516 + ( markdown:: render ( & String :: from_utf8_lossy ( & blob. content) ) ) 517 + } 492 518 } @else { 493 519 ( highlighted_code ( FsPath :: new ( path) , & blob. content, attr_lang) ) 494 520 } 495 521 } 496 522 } 497 523 524 + 525 + fn is_markdown ( filename : & str ) -> bool { 526 + matches ! ( 527 + filename 528 + . rsplit ( '.' ) 529 + . next ( ) 530 + . map ( str :: to_ascii_lowercase) 531 + . as_deref ( ) , 532 + Some ( "md" | "markdown" | "mdown" | "mkd" ) 533 + ) 534 + } 535 + 498 536 499 537 fn highlighted_code ( path : & FsPath , content : & [ u8 ] , attr_lang : Option < & str > ) -> Markup { 500 538 let highlighted = highlight:: highlight ( path, content, attr_lang) ; Expand 692 hidden lines 1193 1231 html ! { 1194 1232 div class="repo-head" { 1195 1233 h1 class="repo-slug" { 1196 - a href={ "/" ( ctx. owner. username) } { ( ctx. owner. username) } "/" 1234 + a href={ "/" ( ctx. owner. username) } { ( ctx. owner. username) } 1235 + span class="slug-sep" { "/" } 1197 1236 a href=( base) { ( ctx. repo. name) } 1198 - @if ctx. repo. is_private { " " span class="badge badge-private" { "private" } } 1237 + @if ctx. repo. is_private { span class="badge badge-private" { "private" } } 1199 1238 } 1200 1239 @if let Some ( desc) = & ctx. repo. description { p class="muted" { ( desc) } } 1201 1240 nav class="tabs repo-tabs" { 1202 1241 ( tab ( Tab :: Code , "Code" , Icon :: File , base. clone ( ) ) ) 1203 1242 ( tab ( Tab :: Branches , "Branches" , Icon :: GitBranch , format ! ( "{base}/-/branches" ) ) ) 1204 1243 ( tab ( Tab :: Tags , "Tags" , Icon :: Box , format ! ( "{base}/-/tags" ) ) ) 1205 - ( tab ( Tab :: Search , "Search" , Icon :: Search , format ! ( "{base}/-/search" ) ) ) 1206 1244 } 1207 - div class="ref-row" { 1208 - 1209 - a class="badge" href=( format ! ( "{base}/-/branches" ) ) { ( rev) } 1210 - a href=( format ! ( "{base}/-/commits/{rev}" ) ) { "Commits" } 1245 + div class="repo-toolbar" { 1246 + a class="btn" href=( format ! ( "{base}/-/branches" ) ) { 1247 + ( icon ( Icon :: GitBranch ) ) span { ( rev) } 1248 + } 1249 + a class="btn" href=( format ! ( "{base}/-/commits/{rev}" ) ) { "Commits" } 1211 1250 } 1212 1251 } 1213 1252 }