ikurotime / gitgud

public
Implement public repository listing feature with explore template
DDavid committed on 2026-08-09 16:26 commit 38f0b63
internal/app/repo_service.go +4 −0
package app
import (
"context"
"fmt"
"regexp"
"strings"
"gitgud/internal/domain"
)
var repoNameRe = regexp.MustCompile(`^[a-z0-9][a-z0-9-_.]{0,99}$`)
type RepoService struct {
repos domain.RepositoryRepository
git domain.GitService
}
func NewRepoService(repos domain.RepositoryRepository, git domain.GitService) *RepoService {
return &RepoService{repos: repos, git: git}
}
func (s *RepoService) CreateRepo(ctx context.Context, owner *domain.User, name, description string, private bool) (*domain.Repository, error) {
name = strings.ToLower(strings.TrimSpace(name))
if name == "." || name == ".." || !repoNameRe.MatchString(name) {
return nil, fmt.Errorf("name must be 1-100 chars of a-z, 0-9, -, _ or . and start with a letter or number: %w", domain.ErrValidation)
}
repo := &domain.Repository{
OwnerID: owner.ID,
OwnerName: owner.Username,
Name: name,
Description: strings.TrimSpace(description),
IsPrivate: private,
DefaultBranch: "main",
}
if err := s.repos.Create(ctx, repo); err != nil {
return nil, err
}
if err := s.git.InitBare(ctx, owner.Username, name, repo.DefaultBranch); err != nil {
_ = s.repos.Delete(ctx, repo.ID)
return nil, fmt.Errorf("init repository on disk: %w", err)
}
return repo, nil
}
func (s *RepoService) Get(ctx context.Context, owner, name string) (*domain.Repository, error) {
return s.repos.ByOwnerAndName(ctx, owner, name)
}
func (s *RepoService) ListByOwner(ctx context.Context, ownerID int64) ([]*domain.Repository, error) {
return s.repos.ListByOwner(ctx, ownerID)
}
+func (s *RepoService) ListPublic(ctx context.Context) ([]*domain.Repository, error) {
+ return s.repos.ListPublic(ctx)
+}
+
func CanView(repo *domain.Repository, viewer *domain.User) error {
if !repo.IsPrivate {
return nil
}
if viewer != nil && viewer.ID == repo.OwnerID {
return nil
}
return domain.ErrNotFound
}
func CanPush(repo *domain.Repository, viewer *domain.User) bool {
return viewer != nil && viewer.ID == repo.OwnerID
}
internal/domain/ports.go +1 −0
package domain
import "context"
type UserRepository interface {
Create(ctx context.Context, u *User) error
ByUsername(ctx context.Context, name string) (*User, error)
ByID(ctx context.Context, id string) (*User, error)
}
type PasswordHasher interface {
Hash(plain string) (string, error)
Compare(hash, plain string) error
}
type RepositoryRepository interface {
Create(ctx context.Context, r *Repository) error
Delete(ctx context.Context, id int64) error
ByOwnerAndName(ctx context.Context, owner, name string) (*Repository, error)
ListByOwner(ctx context.Context, ownerID int64) ([]*Repository, error)
+ ListPublic(ctx context.Context) ([]*Repository, error)
}
type GitService interface {
InitBare(ctx context.Context, owner, name, defaultBranch string) error
RemovePath(ctx context.Context, owner, name string) error
Merge(ctx context.Context, owner, name, base, head, message, authorName, authorEmail string) error
}
type IssueRepository interface {
Create(ctx context.Context, i *Issue) error
ByNumber(ctx context.Context, repoID int64, number int) (*Issue, error)
List(ctx context.Context, repoID int64, state IssueState) ([]*Issue, error)
SetState(ctx context.Context, id int64, state IssueState) error
AddComment(ctx context.Context, c *IssueComment) error
Comments(ctx context.Context, issueID int64) ([]*IssueComment, error)
CountByState(ctx context.Context, repoID int64) (open, closed int, err error)
}
type GitReader interface {
IsEmpty(ctx context.Context, owner, name string) (bool, error)
Branches(ctx context.Context, owner, name string) ([]string, error)
Tip(ctx context.Context, owner, name, ref string) (*Commit, error)
Tree(ctx context.Context, owner, name, ref, path string) ([]TreeEntry, error)
Blob(ctx context.Context, owner, name, ref, path string) (*FileBlob, error)
Log(ctx context.Context, owner, name, ref string, limit, offset int) ([]Commit, error)
CommitDiff(ctx context.Context, owner, name, hash string) (*Commit, []FileDiff, error)
Compare(ctx context.Context, owner, name, base, head string) (*Comparison, error)
}
type PullRequestRepository interface {
Create(ctx context.Context, pr *PullRequest) error
ByNumber(ctx context.Context, repoID int64, number int) (*PullRequest, error)
List(ctx context.Context, repoID int64, state PRState) ([]*PullRequest, error)
SetState(ctx context.Context, id int64, state PRState) error
AddComment(ctx context.Context, c *PRComment) error
Comments(ctx context.Context, prID int64) ([]*PRComment, error)
CountByState(ctx context.Context, repoID int64) (open, merged, closed int, err error)
}
internal/infra/persistence/sqlite/repo_repo.go +15 −1
package sqlite
import (
"context"
"database/sql"
"errors"
"gitgud/internal/domain"
)
type RepoRepo struct {
db *sql.DB
}
func NewRepoRepo(db *sql.DB) *RepoRepo {
return &RepoRepo{db: db}
}
func (r *RepoRepo) Create(ctx context.Context, repo *domain.Repository) error {
res, err := r.db.ExecContext(ctx,
`INSERT INTO repositories(owner_id,name,description,is_private,default_branch) VALUES(?,?,?,?,?)`,
repo.OwnerID, repo.Name, repo.Description, repo.IsPrivate, repo.DefaultBranch)
if err != nil {
if isUniqueViolation(err) {
return domain.ErrConflict
}
return err
}
repo.ID, _ = res.LastInsertId()
return nil
}
func (r *RepoRepo) Delete(ctx context.Context, id int64) error {
_, err := r.db.ExecContext(ctx, `DELETE FROM repositories WHERE id = ?`, id)
return err
}
func (r *RepoRepo) ByOwnerAndName(ctx context.Context, owner, name string) (*domain.Repository, error) {
const q = `SELECT r.id, r.owner_id, u.username, r.name, r.description, r.is_private, r.default_branch, r.created_at
FROM repositories r
JOIN users u ON u.id = r.owner_id
WHERE u.username = ? AND r.name = ?`
var repo domain.Repository
err := r.db.QueryRowContext(ctx, q, owner, name).Scan(
&repo.ID, &repo.OwnerID, &repo.OwnerName, &repo.Name, &repo.Description,
&repo.IsPrivate, &repo.DefaultBranch, &repo.CreatedAt)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, domain.ErrNotFound
}
return nil, err
}
return &repo, nil
}
func (r *RepoRepo) ListByOwner(ctx context.Context, ownerID int64) ([]*domain.Repository, error) {
const q = `SELECT r.id, r.owner_id, u.username, r.name, r.description, r.is_private, r.default_branch, r.created_at
FROM repositories r
JOIN users u ON u.id = r.owner_id
WHERE r.owner_id = ?
ORDER BY r.created_at DESC, r.id DESC`
- rows, err := r.db.QueryContext(ctx, q, ownerID)
+ return r.queryRepos(ctx, q, ownerID)
+}
+
+func (r *RepoRepo) ListPublic(ctx context.Context) ([]*domain.Repository, error) {
+ const q = `SELECT r.id, r.owner_id, u.username, r.name, r.description, r.is_private, r.default_branch, r.created_at
+FROM repositories r
+JOIN users u ON u.id = r.owner_id
+WHERE r.is_private = 0
+ORDER BY r.created_at DESC, r.id DESC`
+
+ return r.queryRepos(ctx, q)
+}
+
+func (r *RepoRepo) queryRepos(ctx context.Context, q string, args ...any) ([]*domain.Repository, error) {
+ rows, err := r.db.QueryContext(ctx, q, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var repos []*domain.Repository
for rows.Next() {
var repo domain.Repository
if err := rows.Scan(
&repo.ID, &repo.OwnerID, &repo.OwnerName, &repo.Name, &repo.Description,
&repo.IsPrivate, &repo.DefaultBranch, &repo.CreatedAt); err != nil {
return nil, err
}
repos = append(repos, &repo)
}
return repos, rows.Err()
}
internal/interface/web/repo_handler.go +3 −9
package web
import (
"errors"
"net/http"
"strings"
"github.com/go-chi/chi"
"gitgud/internal/app"
"gitgud/internal/domain"
"gitgud/internal/interface/web/templates"
)
-func (h *Handlers) dashboard(w http.ResponseWriter, r *http.Request) {
- user := currentUser(r.Context())
- if user == nil {
- render(w, r, http.StatusOK, templates.Home(nil))
- return
- }
-
- repos, err := h.repos.ListByOwner(r.Context(), user.ID)
+func (h *Handlers) explore(w http.ResponseWriter, r *http.Request) {
+ repos, err := h.repos.ListPublic(r.Context())
if err != nil {
h.writeError(w, r, err)
return
}
- render(w, r, http.StatusOK, templates.Dashboard(user, repos))
+ render(w, r, http.StatusOK, templates.Explore(currentUser(r.Context()), repos))
}
func (h *Handlers) showNewRepo(w http.ResponseWriter, r *http.Request) {
render(w, r, http.StatusOK, templates.NewRepo(currentUser(r.Context()), "", "", false, ""))
}
func (h *Handlers) createRepo(w http.ResponseWriter, r *http.Request) {
user := currentUser(r.Context())
name := r.FormValue("name")
description := r.FormValue("description")
private := r.FormValue("private") != ""
repo, err := h.repos.CreateRepo(r.Context(), user, name, description, private)
if err != nil {
status := http.StatusBadRequest
msg := "could not create repository"
switch {
case errors.Is(err, domain.ErrConflict):
status = http.StatusConflict
msg = "a repository with that name already exists"
case errors.Is(err, domain.ErrValidation):
msg = strings.TrimSuffix(err.Error(), ": "+domain.ErrValidation.Error())
}
render(w, r, status, templates.NewRepo(user, name, description, private, msg))
return
}
h.flash(r, "Repository created.")
http.Redirect(w, r, "/"+repo.OwnerName+"/"+repo.Name, http.StatusSeeOther)
}
func (h *Handlers) profile(w http.ResponseWriter, r *http.Request) {
viewer := currentUser(r.Context())
ownerName := chi.URLParam(r, "owner")
owner, err := h.users.ByUsername(r.Context(), ownerName)
if err != nil {
h.notFound(w, r)
return
}
repos, err := h.repos.ListByOwner(r.Context(), owner.ID)
if err != nil {
h.writeError(w, r, err)
return
}
visible := repos[:0]
for _, repo := range repos {
if app.CanView(repo, viewer) == nil {
visible = append(visible, repo)
}
}
render(w, r, http.StatusOK, templates.Profile(viewer, owner.Username, visible))
}
func (h *Handlers) notFound(w http.ResponseWriter, r *http.Request) {
render(w, r, http.StatusNotFound, templates.NotFound(currentUser(r.Context())))
}
internal/interface/web/router.go +1 −1
package web
import (
"embed"
"net/http"
"github.com/a-h/templ"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"gitgud/internal/infra/config"
)
var staticFS embed.FS
func NewRouter(cfg config.Config, h *Handlers) http.Handler {
r := chi.NewRouter()
r.Use(middleware.Logger, middleware.Recoverer)
r.Use(h.sm.LoadAndSave)
r.Use(csrf)
r.Use(h.withFlash)
r.Use(h.withUser)
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
r.Handle("/static/*", http.StripPrefix("/static", http.FileServer((http.FS(staticFS)))))
r.Get("/register", h.showRegister)
r.Post("/register", h.doRegister)
r.Get("/login", h.showLogin)
r.Post("/login", h.doLogin)
r.Post("/logout", h.doLogout)
r.With(h.requireAuth).Get("/new", h.showNewRepo)
r.With(h.requireAuth).Post("/new", h.createRepo)
r.Get("/{owner}/{repo}/info/refs", h.gitHTTP)
r.Post("/{owner}/{repo}/git-upload-pack", h.gitHTTP)
r.Post("/{owner}/{repo}/git-receive-pack", h.gitHTTP)
- r.Get("/", h.dashboard)
+ r.Get("/", h.explore)
r.Get("/{owner}", h.profile)
r.Get("/{owner}/{repo}", h.repoTreeRoot)
r.Get("/{owner}/{repo}/tree/{ref}", h.repoTree)
r.Get("/{owner}/{repo}/tree/{ref}/*", h.repoTree)
r.Get("/{owner}/{repo}/blob/{ref}/*", h.repoBlob)
r.Get("/{owner}/{repo}/raw/{ref}/*", h.repoRaw)
r.Get("/{owner}/{repo}/commits/{ref}", h.repoCommits)
r.Get("/{owner}/{repo}/commit/{hash}", h.repoCommit)
r.Get("/{owner}/{repo}/issues", h.issuesList)
r.With(h.requireAuth).Get("/{owner}/{repo}/issues/new", h.newIssue)
r.With(h.requireAuth).Post("/{owner}/{repo}/issues", h.createIssue)
r.Get("/{owner}/{repo}/issues/{number}", h.issueDetail)
r.With(h.requireAuth).Post("/{owner}/{repo}/issues/{number}/comments", h.addIssueComment)
r.With(h.requireAuth).Post("/{owner}/{repo}/issues/{number}/close", h.closeIssue)
r.With(h.requireAuth).Post("/{owner}/{repo}/issues/{number}/reopen", h.reopenIssue)
r.Get("/{owner}/{repo}/pulls", h.pullsList)
r.Get("/{owner}/{repo}/compare", h.comparePull)
r.With(h.requireAuth).Get("/{owner}/{repo}/pulls/new", h.comparePull)
r.With(h.requireAuth).Post("/{owner}/{repo}/pulls", h.createPull)
r.Get("/{owner}/{repo}/pulls/{number}", h.pullDetail)
r.With(h.requireAuth).Post("/{owner}/{repo}/pulls/{number}/comments", h.addPullComment)
r.With(h.requireAuth).Post("/{owner}/{repo}/pulls/{number}/merge", h.mergePull)
r.With(h.requireAuth).Post("/{owner}/{repo}/pulls/{number}/close", h.closePull)
return r
}
func render(w http.ResponseWriter, r *http.Request, status int, c templ.Component) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(status)
if err := c.Render(r.Context(), w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
internal/interface/web/templates/dashboard.templ +0 −71
-package templates
-
-import "gitgud/internal/domain"
-
-templ Dashboard(user *domain.User, repos []*domain.Repository) {
- @Layout("Dashboard · gitgud", user) {
- <div class="grid gap-6 md:grid-cols-[230px_1fr]">
- <aside class="space-y-5">
- <div class="flex items-center gap-3">
- @avatar(user.Username, "size-9")
- <div class="min-w-0">
- <div class="truncate text-sm font-semibold text-ink">{ user.Username }</div>
- <div class="truncate font-mono text-[11px] text-faint">{ user.Username + "@self-hosted" }</div>
- </div>
- </div>
- <a class="btn btn-primary w-full justify-center" href="/new">+ New repository</a>
- <div>
- <div class="mb-2.5 font-mono text-[11px] font-semibold tracking-wider text-faint uppercase">Repositories</div>
- <div class="flex flex-col gap-0.5 font-mono text-[13px]">
- <span class="rounded bg-panel2 px-2.5 py-1.5 text-ink">all <span class="text-faint">{ itoa(len(repos)) }</span></span>
- <span class="rounded px-2.5 py-1.5 text-muted">public <span class="text-faint">{ itoa(countVisibility(repos, false)) }</span></span>
- <span class="rounded px-2.5 py-1.5 text-muted">private <span class="text-faint">{ itoa(countVisibility(repos, true)) }</span></span>
- </div>
- </div>
- </aside>
- <div>
- <h1 class="mb-4 text-base font-semibold text-ink">Your repositories</h1>
- if len(repos) == 0 {
- @emptyState("No repositories yet", "Create your first repository to start pushing code.", "/new", "Create repository")
- } else {
- @repoList(repos)
- }
- </div>
- </div>
- }
-}
-
-templ repoList(repos []*domain.Repository) {
- <div class="flex flex-col">
- for _, repo := range repos {
- <a class="group block border-t border-line py-3.5 last:border-b" href={ templ.SafeURL("/" + repo.OwnerName + "/" + repo.Name) }>
- <div class="mb-1.5 flex items-center gap-2">
- <span class="font-mono text-sm font-semibold text-accent group-hover:underline">{ repo.OwnerName }/{ repo.Name }</span>
- if repo.IsPrivate {
- <span class="rounded border border-[#4a3d24] px-1.5 py-0.5 font-mono text-[10px] text-warn">private</span>
- } else {
- <span class="rounded border border-line2 px-1.5 py-0.5 font-mono text-[10px] text-faint">public</span>
- }
- </div>
- if repo.Description != "" {
- <p class="mb-2.5 line-clamp-2 text-[13px] text-muted">{ repo.Description }</p>
- }
- <div class="flex items-center gap-1.5 font-mono text-xs text-faint">
- <span class="size-2 rounded-full bg-accent"></span>
- { repo.DefaultBranch }
- </div>
- </a>
- }
- </div>
-}
-
-templ emptyState(title, body, href, cta string) {
- <div class="card flex flex-col items-center px-6 py-14 text-center">
- <div class="mb-4 flex size-10 items-center justify-center rounded-full border border-line2 text-faint">·</div>
- <h3 class="font-semibold text-ink">{ title }</h3>
- <p class="mt-1 max-w-sm text-sm text-muted">{ body }</p>
- if href != "" {
- <a class="btn btn-primary mt-5" href={ templ.SafeURL(href) }>{ cta }</a>
- }
- </div>
-}
internal/interface/web/templates/dashboard_templ.go +0 −360
-// Code generated by templ - DO NOT EDIT.
-
-// templ: version: v0.3.865
-package templates
-
-//lint:file-ignore SA4006 This context is only used if a nested component is present.
-
-import "github.com/a-h/templ"
-import templruntime "github.com/a-h/templ/runtime"
-
-import "gitgud/internal/domain"
-
-func Dashboard(user *domain.User, repos []*domain.Repository) templ.Component {
- return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
- if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
- return templ_7745c5c3_CtxErr
- }
- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
- if !templ_7745c5c3_IsBuffer {
- defer func() {
- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err == nil {
- templ_7745c5c3_Err = templ_7745c5c3_BufErr
- }
- }()
- }
- ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var1 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var1 == nil {
- templ_7745c5c3_Var1 = templ.NopComponent
- }
- ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
- if !templ_7745c5c3_IsBuffer {
- defer func() {
- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err == nil {
- templ_7745c5c3_Err = templ_7745c5c3_BufErr
- }
- }()
- }
- ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"grid gap-6 md:grid-cols-[230px_1fr]\"><aside class=\"space-y-5\"><div class=\"flex items-center gap-3\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = avatar(user.Username, "size-9").Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"min-w-0\"><div class=\"truncate text-sm font-semibold text-ink\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var3 string
- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 12, Col: 74}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div><div class=\"truncate font-mono text-[11px] text-faint\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var4 string
- templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username + "@self-hosted")
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 13, Col: 93}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div></div></div><a class=\"btn btn-primary w-full justify-center\" href=\"/new\">+ New repository</a><div><div class=\"mb-2.5 font-mono text-[11px] font-semibold tracking-wider text-faint uppercase\">Repositories</div><div class=\"flex flex-col gap-0.5 font-mono text-[13px]\"><span class=\"rounded bg-panel2 px-2.5 py-1.5 text-ink\">all <span class=\"text-faint\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var5 string
- templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(repos)))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 20, Col: 108}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span></span> <span class=\"rounded px-2.5 py-1.5 text-muted\">public <span class=\"text-faint\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var6 string
- templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(countVisibility(repos, false)))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 21, Col: 122}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span></span> <span class=\"rounded px-2.5 py-1.5 text-muted\">private <span class=\"text-faint\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var7 string
- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(countVisibility(repos, true)))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 22, Col: 122}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span></span></div></div></aside><div><h1 class=\"mb-4 text-base font-semibold text-ink\">Your repositories</h1>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if len(repos) == 0 {
- templ_7745c5c3_Err = emptyState("No repositories yet", "Create your first repository to start pushing code.", "/new", "Create repository").Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- templ_7745c5c3_Err = repoList(repos).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
- templ_7745c5c3_Err = Layout("Dashboard · gitgud", user).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
-}
-
-func repoList(repos []*domain.Repository) templ.Component {
- return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
- if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
- return templ_7745c5c3_CtxErr
- }
- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
- if !templ_7745c5c3_IsBuffer {
- defer func() {
- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err == nil {
- templ_7745c5c3_Err = templ_7745c5c3_BufErr
- }
- }()
- }
- ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var8 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var8 == nil {
- templ_7745c5c3_Var8 = templ.NopComponent
- }
- ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"flex flex-col\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- for _, repo := range repos {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<a class=\"group block border-t border-line py-3.5 last:border-b\" href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var9 templ.SafeURL = templ.SafeURL("/" + repo.OwnerName + "/" + repo.Name)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\"><div class=\"mb-1.5 flex items-center gap-2\"><span class=\"font-mono text-sm font-semibold text-accent group-hover:underline\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var10 string
- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(repo.OwnerName)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 43, Col: 101}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "/")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var11 string
- templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(repo.Name)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 43, Col: 115}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span> ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if repo.IsPrivate {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span class=\"rounded border border-[#4a3d24] px-1.5 py-0.5 font-mono text-[10px] text-warn\">private</span>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span class=\"rounded border border-line2 px-1.5 py-0.5 font-mono text-[10px] text-faint\">public</span>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if repo.Description != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<p class=\"mb-2.5 line-clamp-2 text-[13px] text-muted\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var12 string
- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(repo.Description)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 51, Col: 77}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</p>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"flex items-center gap-1.5 font-mono text-xs text-faint\"><span class=\"size-2 rounded-full bg-accent\"></span> ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var13 string
- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(repo.DefaultBranch)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 55, Col: 25}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div></a>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
-}
-
-func emptyState(title, body, href, cta string) templ.Component {
- return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
- if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
- return templ_7745c5c3_CtxErr
- }
- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
- if !templ_7745c5c3_IsBuffer {
- defer func() {
- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err == nil {
- templ_7745c5c3_Err = templ_7745c5c3_BufErr
- }
- }()
- }
- ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var14 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var14 == nil {
- templ_7745c5c3_Var14 = templ.NopComponent
- }
- ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"card flex flex-col items-center px-6 py-14 text-center\"><div class=\"mb-4 flex size-10 items-center justify-center rounded-full border border-line2 text-faint\">·</div><h3 class=\"font-semibold text-ink\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var15 string
- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(title)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 65, Col: 44}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</h3><p class=\"mt-1 max-w-sm text-sm text-muted\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var16 string
- templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(body)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 66, Col: 52}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if href != "" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<a class=\"btn btn-primary mt-5\" href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var17 templ.SafeURL = templ.SafeURL(href)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var17)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var18 string
- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(cta)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/dashboard.templ`, Line: 68, Col: 69}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</a>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</div>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
-}
-
-var _ = templruntime.GeneratedTemplate
internal/interface/web/templates/explore.templ +65 −0
+package templates
+
+import "gitgud/internal/domain"
+
+templ Explore(user *domain.User, repos []*domain.Repository) {
+ @Layout("Explore · gitgud", user) {
+ <div class="mx-auto max-w-3xl">
+ <div class="mb-6 flex items-center justify-between gap-4">
+ <div>
+ <h1 class="text-base font-semibold text-ink">Public repositories</h1>
+ <p class="mt-1 text-[13px] text-muted">
+ { itoa(len(repos)) } public { plural(len(repos), "repository", "repositories") } on this instance.
+ </p>
+ </div>
+ if user != nil {
+ <a class="btn btn-primary shrink-0" href="/new">+ New repository</a>
+ }
+ </div>
+ if len(repos) == 0 {
+ if user != nil {
+ @emptyState("No public repositories yet", "Create a public repository and it will show up here for everyone.", "/new", "Create repository")
+ } else {
+ @emptyState("No public repositories yet", "When someone creates a public repository, it will appear here.", "", "")
+ }
+ } else {
+ @repoList(repos)
+ }
+ </div>
+ }
+}
+
+templ repoList(repos []*domain.Repository) {
+ <div class="flex flex-col">
+ for _, repo := range repos {
+ <a class="group block border-t border-line py-3.5 last:border-b" href={ templ.SafeURL("/" + repo.OwnerName + "/" + repo.Name) }>
+ <div class="mb-1.5 flex items-center gap-2">
+ <span class="font-mono text-sm font-semibold text-accent group-hover:underline">{ repo.OwnerName }/{ repo.Name }</span>
+ if repo.IsPrivate {
+ <span class="rounded border border-[#4a3d24] px-1.5 py-0.5 font-mono text-[10px] text-warn">private</span>
+ } else {
+ <span class="rounded border border-line2 px-1.5 py-0.5 font-mono text-[10px] text-faint">public</span>
+ }
+ </div>
+ if repo.Description != "" {
+ <p class="mb-2.5 line-clamp-2 text-[13px] text-muted">{ repo.Description }</p>
+ }
+ <div class="flex items-center gap-1.5 font-mono text-xs text-faint">
+ <span class="size-2 rounded-full bg-accent"></span>
+ { repo.DefaultBranch }
+ </div>
+ </a>
+ }
+ </div>
+}
+
+templ emptyState(title, body, href, cta string) {
+ <div class="card flex flex-col items-center px-6 py-14 text-center">
+ <div class="mb-4 flex size-10 items-center justify-center rounded-full border border-line2 text-faint">·</div>
+ <h3 class="font-semibold text-ink">{ title }</h3>
+ <p class="mt-1 max-w-sm text-sm text-muted">{ body }</p>
+ if href != "" {
+ <a class="btn btn-primary mt-5" href={ templ.SafeURL(href) }>{ cta }</a>
+ }
+ </div>
+}
internal/interface/web/templates/explore_templ.go +330 −0
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.865
+package templates
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "gitgud/internal/domain"
+
+func Explore(user *domain.User, repos []*domain.Repository) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"mx-auto max-w-3xl\"><div class=\"mb-6 flex items-center justify-between gap-4\"><div><h1 class=\"text-base font-semibold text-ink\">Public repositories</h1><p class=\"mt-1 text-[13px] text-muted\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(itoa(len(repos)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 12, Col: 24}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " public ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(plural(len(repos), "repository", "repositories"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 12, Col: 84}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " on this instance.</p></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if user != nil {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<a class=\"btn btn-primary shrink-0\" href=\"/new\">+ New repository</a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(repos) == 0 {
+ if user != nil {
+ templ_7745c5c3_Err = emptyState("No public repositories yet", "Create a public repository and it will show up here for everyone.", "/new", "Create repository").Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = emptyState("No public repositories yet", "When someone creates a public repository, it will appear here.", "", "").Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ } else {
+ templ_7745c5c3_Err = repoList(repos).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Layout("Explore · gitgud", user).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func repoList(repos []*domain.Repository) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var5 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var5 == nil {
+ templ_7745c5c3_Var5 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"flex flex-col\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, repo := range repos {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a class=\"group block border-t border-line py-3.5 last:border-b\" href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 templ.SafeURL = templ.SafeURL("/" + repo.OwnerName + "/" + repo.Name)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"><div class=\"mb-1.5 flex items-center gap-2\"><span class=\"font-mono text-sm font-semibold text-accent group-hover:underline\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(repo.OwnerName)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 37, Col: 101}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "/")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 string
+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(repo.Name)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 37, Col: 115}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if repo.IsPrivate {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span class=\"rounded border border-[#4a3d24] px-1.5 py-0.5 font-mono text-[10px] text-warn\">private</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<span class=\"rounded border border-line2 px-1.5 py-0.5 font-mono text-[10px] text-faint\">public</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if repo.Description != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p class=\"mb-2.5 line-clamp-2 text-[13px] text-muted\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var9 string
+ templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(repo.Description)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 45, Col: 77}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</p>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"flex items-center gap-1.5 font-mono text-xs text-faint\"><span class=\"size-2 rounded-full bg-accent\"></span> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var10 string
+ templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(repo.DefaultBranch)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 49, Col: 25}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func emptyState(title, body, href, cta string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var11 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var11 == nil {
+ templ_7745c5c3_Var11 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"card flex flex-col items-center px-6 py-14 text-center\"><div class=\"mb-4 flex size-10 items-center justify-center rounded-full border border-line2 text-faint\">·</div><h3 class=\"font-semibold text-ink\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var12 string
+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 59, Col: 44}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</h3><p class=\"mt-1 max-w-sm text-sm text-muted\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var13 string
+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(body)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 60, Col: 52}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if href != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<a class=\"btn btn-primary mt-5\" href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var14 templ.SafeURL = templ.SafeURL(href)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var14)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var15 string
+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(cta)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/explore.templ`, Line: 62, Col: 69}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
internal/interface/web/templates/helpers.go +7 −0
package templates
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"gitgud/internal/domain"
"gitgud/internal/interface/web/presenter"
)
type ctxKey int
const (
flashKey ctxKey = iota
csrfKey
)
func WithFlash(ctx context.Context, msg string) context.Context {
return context.WithValue(ctx, flashKey, msg)
}
func flashOf(ctx context.Context) string {
s, _ := ctx.Value(flashKey).(string)
return s
}
func WithCSRF(ctx context.Context, token string) context.Context {
return context.WithValue(ctx, csrfKey, token)
}
func csrfToken(ctx context.Context) string {
s, _ := ctx.Value(csrfKey).(string)
return s
}
func markdown(s string) string {
return presenter.RenderMarkdown([]byte(s))
}
func fmtTime(t time.Time) string {
return t.Format("2006-01-02 15:04")
}
func stateAction(state domain.IssueState) string {
if state == domain.IssueOpen {
return "/close"
}
return "/reopen"
}
func stateTabClass(current, want string) string {
if current == want {
return "font-semibold text-ink"
}
return "text-muted hover:text-ink"
}
// baseURL is the external URL of this gitgud instance, used to build clone
// URLs shown in the UI. It defaults to the local dev address and is overridden
// at startup via SetBaseURL from config (GITGUD_BASE_URL).
var baseURL = "http://localhost:8080"
// SetBaseURL configures the external base URL used in clone instructions.
func SetBaseURL(u string) {
if u != "" {
baseURL = strings.TrimRight(u, "/")
}
}
func cloneInstructions(repo *domain.Repository) string {
url := baseURL + "/" + repo.OwnerName + "/" + repo.Name + ".git"
return fmt.Sprintf(`git clone %s
cd %s
echo "# %s" > README.md
git add README.md
git commit -m "first commit"
git push -u origin %s`, url, repo.Name, repo.Name, repo.DefaultBranch)
}
type Crumb struct {
Name string
Href string
}
func treeCrumbs(repo *domain.Repository, ref, p string) []Crumb {
base := "/" + repo.OwnerName + "/" + repo.Name + "/tree/" + ref
crumbs := []Crumb{{Name: repo.Name, Href: base}}
if p = strings.Trim(p, "/"); p != "" {
acc := base
for _, seg := range strings.Split(p, "/") {
acc += "/" + seg
crumbs = append(crumbs, Crumb{Name: seg, Href: acc})
}
}
return crumbs
}
func entryHref(repo *domain.Repository, ref string, e domain.TreeEntry) string {
kind := "blob"
if e.IsDir {
kind = "tree"
}
return "/" + repo.OwnerName + "/" + repo.Name + "/" + kind + "/" + ref + "/" + e.Path
}
func repoPath(repo *domain.Repository, sub string) string {
return "/" + repo.OwnerName + "/" + repo.Name + sub
}
func humanSize(n int64) string {
const unit = 1024
if n < unit {
return fmt.Sprintf("%d B", n)
}
div, exp := int64(unit), 0
for x := n / unit; x >= unit; x /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(n)/float64(div), "KMGTPE"[exp])
}
func itoa(n int) string {
return strconv.Itoa(n)
}
+func plural(n int, one, many string) string {
+ if n == 1 {
+ return one
+ }
+ return many
+}
+
func countVisibility(repos []*domain.Repository, private bool) int {
n := 0
for _, r := range repos {
if r.IsPrivate == private {
n++
}
}
return n
}
func initial(s string) string {
if s == "" {
return "?"
}
return strings.ToUpper(s[:1])
}
func firstLine(s string) string {
if i := strings.IndexByte(s, '\n'); i >= 0 {
return s[:i]
}
return s
}
func diffLineClass(line string) string {
switch {
case strings.HasPrefix(line, "@@"):
return "diff-hunk"
case strings.HasPrefix(line, "+"):
return "diff-add"
case strings.HasPrefix(line, "-"):
return "diff-del"
default:
return "text-muted"
}
}
// statBlocks renders a 5-square GitHub-style diffstat: green for additions,
// red for deletions, proportional to the change, padded with neutral squares.
func statBlocks(added, deleted int) []string {
blocks := make([]string, 5)
total := added + deleted
if total == 0 {
for i := range blocks {
blocks[i] = "none"
}
return blocks
}
greens := added * 5 / total
if added > 0 && greens == 0 {
greens = 1
}
reds := 5 - greens
if deleted > 0 && reds == 0 && greens > 0 {
greens--
reds = 1
}
for i := range blocks {
switch {
case i < greens:
blocks[i] = "add"
case i < greens+reds:
blocks[i] = "del"
default:
blocks[i] = "none"
}
}
return blocks
}
// langLabel maps a file path to a human language label for the blob header.
func langLabel(path string) string {
ext := strings.ToLower(path)
if i := strings.LastIndexByte(ext, '.'); i >= 0 {
ext = ext[i+1:]
}
switch ext {
case "go":
return "Go"
case "js", "mjs", "cjs":
return "JavaScript"
case "ts", "tsx":
return "TypeScript"
case "py":
return "Python"
case "rs":
return "Rust"
case "rb":
return "Ruby"
case "java":
return "Java"
case "c", "h":
return "C"
case "cpp", "cc", "hpp":
return "C++"
case "sh", "bash", "zsh":
return "Shell"
case "html", "htm":
return "HTML"
case "css":
return "CSS"
case "json":
return "JSON"
case "yml", "yaml":
return "YAML"
case "md", "markdown":
return "Markdown"
case "sql":
return "SQL"
case "templ":
return "Templ"
case "":
return "Text"
default:
return strings.ToUpper(ext)
}
}
func diffLines(patch string) []string {
return strings.Split(strings.TrimRight(patch, "\n"), "\n")
}
internal/interface/web/templates/home.templ +0 −26
-package templates
-
-import "gitgud/internal/domain"
-
-templ Home(user *domain.User) {
- @Layout("gitgud — minimal git hosting", user) {
- <div class="mx-auto max-w-2xl py-16 text-center">
- <div class="mx-auto mb-8 flex size-14 items-center justify-center rounded-2xl border border-accent/30 bg-accent/[.08] text-accent shadow-[0_0_40px_-6px_rgba(63,207,127,.6)]">
- @gitLogo("size-7")
- </div>
- <h1 class="text-4xl font-bold tracking-tight text-ink sm:text-5xl">
- git hosting,<br/><span class="text-accent">stripped down.</span>
- </h1>
- <p class="mx-auto mt-5 max-w-md text-muted">
- Clone, push, browse, and review — a small, self-hosted home for your repositories.
- </p>
- <div class="mt-8 flex items-center justify-center gap-3">
- <a class="btn btn-primary" href="/register">Get started</a>
- <a class="btn" href="/login">Sign in</a>
- </div>
- <div class="mx-auto mt-12 max-w-md rounded-lg border border-line bg-panel2 px-4 py-3 text-left font-mono text-xs text-muted">
- <span class="text-faint">$</span> git clone { baseURL }/<span class="text-accent">you</span>/<span class="text-accent">repo</span>.git
- </div>
- </div>
- }
-}
internal/interface/web/templates/home_templ.go +0 −81
-// Code generated by templ - DO NOT EDIT.
-
-// templ: version: v0.3.865
-package templates
-
-//lint:file-ignore SA4006 This context is only used if a nested component is present.
-
-import "github.com/a-h/templ"
-import templruntime "github.com/a-h/templ/runtime"
-
-import "gitgud/internal/domain"
-
-func Home(user *domain.User) templ.Component {
- return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
- if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
- return templ_7745c5c3_CtxErr
- }
- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
- if !templ_7745c5c3_IsBuffer {
- defer func() {
- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err == nil {
- templ_7745c5c3_Err = templ_7745c5c3_BufErr
- }
- }()
- }
- ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var1 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var1 == nil {
- templ_7745c5c3_Var1 = templ.NopComponent
- }
- ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
- templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
- templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
- if !templ_7745c5c3_IsBuffer {
- defer func() {
- templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err == nil {
- templ_7745c5c3_Err = templ_7745c5c3_BufErr
- }
- }()
- }
- ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"mx-auto max-w-2xl py-16 text-center\"><div class=\"mx-auto mb-8 flex size-14 items-center justify-center rounded-2xl border border-accent/30 bg-accent/[.08] text-accent shadow-[0_0_40px_-6px_rgba(63,207,127,.6)]\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = gitLogo("size-7").Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><h1 class=\"text-4xl font-bold tracking-tight text-ink sm:text-5xl\">git hosting,<br><span class=\"text-accent\">stripped down.</span></h1><p class=\"mx-auto mt-5 max-w-md text-muted\">Clone, push, browse, and review — a small, self-hosted home for your repositories.</p><div class=\"mt-8 flex items-center justify-center gap-3\"><a class=\"btn btn-primary\" href=\"/register\">Get started</a> <a class=\"btn\" href=\"/login\">Sign in</a></div><div class=\"mx-auto mt-12 max-w-md rounded-lg border border-line bg-panel2 px-4 py-3 text-left font-mono text-xs text-muted\"><span class=\"text-faint\">$</span> git clone ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var3 string
- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(baseURL)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/interface/web/templates/home.templ`, Line: 22, Col: 57}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "/<span class=\"text-accent\">you</span>/<span class=\"text-accent\">repo</span>.git</div></div>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
- templ_7745c5c3_Err = Layout("gitgud — minimal git hosting", user).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
-}
-
-var _ = templruntime.GeneratedTemplate