package main import ( "bytes" "fmt" "html" "log" "net/http" "net/url" "sort" "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) Homepage(w http.ResponseWriter, r *http.Request) { ctx := r.Context() repos, err := this.repos(ctx) if err != nil { this.internalError(w, r, fmt.Errorf("listing repos: %w", err)) return } topics := make(map[string][]string) for _, repo := range repos { if t, err := this.topicsForRepo(ctx, repo.Name); err == nil { topics[repo.Name] = t } } // Sort repos once alphabetically, to get alphabetical indexes... sort.Slice(repos, func(i, j int) bool { return repos[i].Name < repos[j].Name }) alphabeticalOrderIndexes := make(map[string]int, len(repos)) for idx, repo := range repos { alphabeticalOrderIndexes[repo.Name] = idx } // But then make sure the final sort is by most-recently-created sort.Slice(repos, func(i, j int) bool { return repos[i].CreatedAt.After(repos[j].CreatedAt) }) // Ready for template this.Templatepage(w, r, "", "", func() { fmt.Fprint(w, ` `+this.cfg.Template.HomepageHeaderHTML+`

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

`) for _, repo := range repos { 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 topics[repo.Name] { rowClass += `taggedWith-` + topic + ` ` } fmt.Fprint(w, ` `) } fmt.Fprint(w, `
`+html.EscapeString(repo.Name)+``+html.EscapeString(normalisedDesc)+` more...
`) for _, topic := range topics[repo.Name] { 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") // We add some extra badges based on special text entries extraBadgesMd := ` ![](https://img.shields.io/badge/build-success-brightgreen)` 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 } 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 }) }