package main import ( "bytes" "fmt" "html" "log" "net/http" "net/url" "strings" ) func (this *Application) Templatepage(w http.ResponseWriter, r *http.Request, pageDesc, extraHead string, cb func()) { pageTitle := this.cfg.Template.AppName if pageDesc != "" { pageTitle = pageDesc + ` | ` + pageTitle } w.Header().Set(`Content-Type`, `text/html; charset=UTF-8`) w.WriteHeader(200) fmt.Fprint(w, ` `+html.EscapeString(pageTitle)+` `+extraHead+`

`+html.EscapeString(this.cfg.Template.AppName)+`

`) cb() fmt.Fprint(w, ` `) } func (this *Application) internalError(w http.ResponseWriter, r *http.Request, err error) { log.Printf("%s %s: %s", r.Method, r.URL.Path, err) http.Error(w, "An internal error occurred.", 500) } func (this *Application) Delay(w http.ResponseWriter, r *http.Request) { this.Templatepage(w, r, "Loading...", "", func() { fmt.Fprintf(w, `

Loading, please wait...

`) }) } func (this *Application) Homepage(w http.ResponseWriter, r *http.Request) { this.reposMut.RLock() defer this.reposMut.RUnlock() if len(this.reposCache) == 0 { // We haven't loaded the repositories from Gitea yet this.Delay(w, r) return } // Ready for template this.Templatepage(w, r, "", "", func() { fmt.Fprint(w, ` `+this.cfg.Template.HomepageHeaderHTML+`

Projects (`+fmt.Sprintf("%d", len(this.reposCache))+`)

`) for _, repo := range this.reposCache { pageHref := html.EscapeString(`/` + url.PathEscape(repo.Name)) normalisedDesc := repo.Description normalisedDesc = strings.TrimRight(repo.Description, `.`) if len(normalisedDesc) > 0 { // Lowercase the first letter of the description, unless it starts with an acronym (all letters uppercase first word) or CamelCase word firstWord := strings.SplitN(normalisedDesc, " ", 2)[0] isAcronymOrCamelCase := len(firstWord) > 1 && (firstWord[1:] != strings.ToLower(firstWord[1:])) if !(isAcronymOrCamelCase || firstWord == `Go`) { normalisedDesc = strings.ToLower(normalisedDesc[0:1]) + normalisedDesc[1:] } // Add leading `` to separate from the repo title normalisedDesc = `, ` + normalisedDesc } rowClass := "" for _, topic := range repo.topics { rowClass += `taggedWith-` + topic + ` ` } fmt.Fprint(w, ` `) } fmt.Fprint(w, `
`+html.EscapeString(repo.Name)+``+html.EscapeString(normalisedDesc)+` more...
`) for _, topic := range repo.topics { fmt.Fprint(w, ``+html.EscapeString(topic)+` `) } fmt.Fprint(w, `
`) }) } func (this *Application) Bannerpage(w http.ResponseWriter, r *http.Request, repoName string) { ctx := r.Context() images, err := this.imageFilesForRepo(ctx, repoName) if err != nil { this.internalError(w, r, fmt.Errorf("listing images: %w", err)) return } if len(images) == 0 { w.Header().Set(`Location`, `/static/no_image.png`) w.WriteHeader(301) return } w.Header().Set(`Location`, images[0].RawURL) w.WriteHeader(301) } func (this *Application) Repopage(w http.ResponseWriter, r *http.Request, repoName string) { ctx := r.Context() repoURL := this.cfg.Gitea.URL + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repoName) extraHead := "" readme, err := this.repoFile(ctx, repoName, `README.md`) if err != nil { this.internalError(w, r, fmt.Errorf("loading README.md: %w", err)) return } lines := strings.Split(string(readme), "\n") // Check if this repo has the 'article' tag hasArticleTag := false this.reposMut.RLock() for _, rr := range this.reposCache { if rr.Name != repoName { continue } for _, topic := range rr.topics { if topic == "article" { hasArticleTag = true break } } } this.reposMut.RUnlock() // We add some extra badges based on special text entries extraBadgesMd := `` if !hasArticleTag { extraBadgesMd += ` %%REPLACEME__BADGE%%` } extraBadgesMd += ` [![](https://img.shields.io/badge/vcs-git-green?logo=git)](` + repoURL + `)` // Inject more badges to 3rd line; or, create badges on 3rd line if there are none already if len(lines) >= 3 && strings.Contains(lines[2], `shields.io`) { lines[2] += ` ` + extraBadgesMd } else { // Push other lines down lines = append([]string{lines[0], lines[1], extraBadgesMd, ""}, lines[2:]...) } readmeHtml, err := this.renderMarkdown(ctx, repoName, strings.Join(lines, "\n")) if err != nil { this.internalError(w, r, fmt.Errorf("rendering markdown: %w", err)) return } readmeHtml = []byte(strings.Replace(string(readmeHtml), `%%REPLACEME__BADGE%%`, ``, 1)) images, err := this.imageFilesForRepo(ctx, repoName) if err != nil { this.internalError(w, r, fmt.Errorf("listing images: %w", err)) return } // If the Git repository contains a top-level go.mod file, allow vanity imports if goMod, err := this.repoFile(ctx, repoName, `go.mod`); err == nil { // Check the first line should be `module MODULENAME\n` firstLine := bytes.SplitN(goMod, []byte("\n"), 2)[0] if bytes.HasPrefix(firstLine, []byte("module ")) { moduleName := firstLine[7:] extraHead = `` } } // De-escalate all headers in rendered markdown to match our style repl := strings.NewReplacer(``, ``, ``, ``, ``, ``) // Ready for template this.Templatepage(w, r, repoName, extraHead, func() { projBodyclass := `projbody` if len(images) > 0 { projBodyclass += ` projbody_halfw` } fmt.Fprint(w, `
`) repl.WriteString(w, string(readmeHtml)) fmt.Fprint(w, `
`) if len(images) > 0 { fmt.Fprint(w, `
`) for _, img := range images { fmt.Fprint(w, ``) } fmt.Fprint(w, `
`) } fmt.Fprint(w, `
`) fmt.Fprint(w, `
`) // projbody }) }