forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview: pages/blob: toggle render for markdown files

Also sets up a pages/markup package with extensible rendering for future markup languages that we may support.
We may also want an interface or something for markup.RenderMarkdown (maybe just markup.Render), but don't have to bother with that now.

authored by anirudh.fi and committed by Tangled b328b08d 9cb8591e

Changed files
+67 -9
appview
pages
state
+2 -1
appview/pages/funcmap.go
··· 13 "time" 14 15 "github.com/dustin/go-humanize" 16 ) 17 18 func funcMap() template.FuncMap { ··· 137 return v.Slice(start, end).Interface() 138 }, 139 "markdown": func(text string) template.HTML { 140 - return template.HTML(renderMarkdown(text)) 141 }, 142 "isNil": func(t any) bool { 143 // returns false for other "zero" values
··· 13 "time" 14 15 "github.com/dustin/go-humanize" 16 + "tangled.sh/tangled.sh/core/appview/pages/markup" 17 ) 18 19 func funcMap() template.FuncMap { ··· 138 return v.Slice(start, end).Interface() 139 }, 140 "markdown": func(text string) template.HTML { 141 + return template.HTML(markup.RenderMarkdown(text)) 142 }, 143 "isNil": func(t any) bool { 144 // returns false for other "zero" values
+3 -2
appview/pages/markdown.go appview/pages/markup/markdown.go
··· 1 - package pages 2 3 import ( 4 "bytes" ··· 8 "github.com/yuin/goldmark/parser" 9 ) 10 11 - func renderMarkdown(source string) string { 12 md := goldmark.New( 13 goldmark.WithExtensions(extension.GFM), 14 goldmark.WithParserOptions(
··· 1 + // Package markup is an umbrella package for all markups and their renderers. 2 + package markup 3 4 import ( 5 "bytes" ··· 9 "github.com/yuin/goldmark/parser" 10 ) 11 12 + func RenderMarkdown(source string) string { 13 md := goldmark.New( 14 goldmark.WithExtensions(extension.GFM), 15 goldmark.WithParserOptions(
+26
appview/pages/markup/readme.go
···
··· 1 + package markup 2 + 3 + import "strings" 4 + 5 + type Format string 6 + 7 + const ( 8 + FormatMarkdown Format = "markdown" 9 + FormatText Format = "text" 10 + ) 11 + 12 + var FileTypes map[Format][]string = map[Format][]string{ 13 + FormatMarkdown: []string{".md", ".markdown", ".mdown", ".mkdn", ".mkd"}, 14 + } 15 + 16 + func GetFormat(filename string) Format { 17 + for format, extensions := range FileTypes { 18 + for _, extension := range extensions { 19 + if strings.HasSuffix(filename, extension) { 20 + return format 21 + } 22 + } 23 + } 24 + // default format 25 + return FormatText 26 + }
+15 -5
appview/pages/pages.go
··· 24 "github.com/microcosm-cc/bluemonday" 25 "tangled.sh/tangled.sh/core/appview/auth" 26 "tangled.sh/tangled.sh/core/appview/db" 27 "tangled.sh/tangled.sh/core/appview/state/userutil" 28 "tangled.sh/tangled.sh/core/types" 29 ) ··· 350 ext := filepath.Ext(params.ReadmeFileName) 351 switch ext { 352 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 353 - htmlString = renderMarkdown(params.Readme) 354 params.Raw = false 355 params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString)) 356 default: ··· 446 } 447 448 type RepoBlobParams struct { 449 - LoggedInUser *auth.User 450 - RepoInfo RepoInfo 451 - Active string 452 - BreadCrumbs [][]string 453 types.RepoBlobResponse 454 } 455 ··· 458 b := style.Builder() 459 b.Add(chroma.LiteralString, "noitalic") 460 style, _ = b.Build() 461 462 if params.Lines < 5000 { 463 c := params.Contents
··· 24 "github.com/microcosm-cc/bluemonday" 25 "tangled.sh/tangled.sh/core/appview/auth" 26 "tangled.sh/tangled.sh/core/appview/db" 27 + "tangled.sh/tangled.sh/core/appview/pages/markup" 28 "tangled.sh/tangled.sh/core/appview/state/userutil" 29 "tangled.sh/tangled.sh/core/types" 30 ) ··· 351 ext := filepath.Ext(params.ReadmeFileName) 352 switch ext { 353 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 354 + htmlString = markup.RenderMarkdown(params.Readme) 355 params.Raw = false 356 params.HTMLReadme = template.HTML(bluemonday.UGCPolicy().Sanitize(htmlString)) 357 default: ··· 447 } 448 449 type RepoBlobParams struct { 450 + LoggedInUser *auth.User 451 + RepoInfo RepoInfo 452 + Active string 453 + BreadCrumbs [][]string 454 + ShowRendered bool 455 + RenderedContents template.HTML 456 types.RepoBlobResponse 457 } 458 ··· 461 b := style.Builder() 462 b.Add(chroma.LiteralString, "noitalic") 463 style, _ = b.Build() 464 + 465 + if params.ShowRendered { 466 + switch markup.GetFormat(params.Path) { 467 + case markup.FormatMarkdown: 468 + params.RenderedContents = template.HTML(markup.RenderMarkdown(params.Contents)) 469 + } 470 + } 471 472 if params.Lines < 5000 { 473 c := params.Contents
+10 -1
appview/pages/templates/repo/blob.html
··· 43 <span>{{ byteFmt .SizeHint }}</span> 44 <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 45 <a href="/{{ .RepoInfo.FullName }}/blob/{{ .Ref }}/raw/{{ .Path }}">view raw</a> 46 </div> 47 </div> 48 </div> ··· 52 </p> 53 {{ else }} 54 <div class="overflow-auto relative"> 55 - <div class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ $.Contents | escapeHtml }}</div> 56 </div> 57 {{ end }} 58 {{ end }}
··· 43 <span>{{ byteFmt .SizeHint }}</span> 44 <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 45 <a href="/{{ .RepoInfo.FullName }}/blob/{{ .Ref }}/raw/{{ .Path }}">view raw</a> 46 + <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 47 + <a 48 + href="/{{ .RepoInfo.FullName }}/blob/{{ .Ref }}/{{ .Path }}?code={{ .ShowRendered }}" 49 + hx-boost="true" 50 + >view {{ if .ShowRendered }}code{{ else }}rendered{{ end }}</a> 51 </div> 52 </div> 53 </div> ··· 57 </p> 58 {{ else }} 59 <div class="overflow-auto relative"> 60 + {{ if .ShowRendered }} 61 + <div id="blob-contents" class="prose dark:prose-invert p-6">{{ .RenderedContents }}</div> 62 + {{ else }} 63 + <div id="blob-contents" class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ $.Contents | escapeHtml }}</div> 64 + {{ end }} 65 </div> 66 {{ end }} 67 {{ end }}
+11
appview/state/repo.go
··· 25 "tangled.sh/tangled.sh/core/appview/auth" 26 "tangled.sh/tangled.sh/core/appview/db" 27 "tangled.sh/tangled.sh/core/appview/pages" 28 "tangled.sh/tangled.sh/core/types" 29 30 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 453 } 454 } 455 456 user := s.auth.GetUser(r) 457 s.pages.RepoBlob(w, pages.RepoBlobParams{ 458 LoggedInUser: user, 459 RepoInfo: f.RepoInfo(s, user), 460 RepoBlobResponse: result, 461 BreadCrumbs: breadcrumbs, 462 }) 463 return 464 }
··· 25 "tangled.sh/tangled.sh/core/appview/auth" 26 "tangled.sh/tangled.sh/core/appview/db" 27 "tangled.sh/tangled.sh/core/appview/pages" 28 + "tangled.sh/tangled.sh/core/appview/pages/markup" 29 "tangled.sh/tangled.sh/core/types" 30 31 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 454 } 455 } 456 457 + var showRendered = false 458 + if markup.GetFormat(result.Path) == markup.FormatMarkdown { 459 + showRendered = true 460 + } 461 + 462 + if r.URL.Query().Get("code") == "true" { 463 + showRendered = false 464 + } 465 + 466 user := s.auth.GetUser(r) 467 s.pages.RepoBlob(w, pages.RepoBlobParams{ 468 LoggedInUser: user, 469 RepoInfo: f.RepoInfo(s, user), 470 RepoBlobResponse: result, 471 BreadCrumbs: breadcrumbs, 472 + ShowRendered: showRendered, 473 }) 474 return 475 }