| @@ -83,35 +83,61 @@ func (s *Server) describeAll(repos []store.Repo) []describedRepo { |
| 83 | return out |
83 | return out |
| 84 | } |
84 | } |
| 85 | |
85 | |
| |
86 | // index is the homepage: a dashboard for logged-in users, a landing page |
| |
87 | // for everyone else. The full public listing lives at /explore. |
| 86 | func (s *Server) index(w http.ResponseWriter, r *http.Request) { |
88 | func (s *Server) index(w http.ResponseWriter, r *http.Request) { |
| |
89 | if s.cfg.Web.Mode == "accounts" { |
| |
90 | if viewer := s.viewer(r); viewer.ID != 0 { |
| |
91 | s.dashboard(w, r, viewer) |
| |
92 | return |
| |
93 | } |
| |
94 | } |
| |
95 | host := strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix( |
| |
96 | s.cfg.Server.SiteURL, "https://"), "http://"), "/") |
| |
97 | s.render(w, "landing.html", struct { |
| |
98 | Site string |
| |
99 | Host string |
| |
100 | Accounts bool |
| |
101 | }{s.siteName(), host, s.cfg.Web.Mode == "accounts"}) |
| |
102 | } |
| |
103 | |
| |
104 | func (s *Server) dashboard(w http.ResponseWriter, r *http.Request, viewer store.User) { |
| |
105 | pinned, _ := s.st.PinnedRepos(viewer.ID) |
| |
106 | var visible []store.Repo |
| |
107 | for _, rp := range pinned { |
| |
108 | grant, _ := s.st.AccessRole(rp.ID, viewer.ID) |
| |
109 | if policy.CanRead(viewer, rp, grant) { |
| |
110 | visible = append(visible, rp) |
| |
111 | } |
| |
112 | } |
| |
113 | mrs, _ := s.st.DashboardMRs(viewer.ID) |
| |
114 | issues, _ := s.st.DashboardIssues(viewer.ID) |
| |
115 | s.render(w, "dashboard.html", struct { |
| |
116 | Site string |
| |
117 | Viewer string |
| |
118 | Pinned []describedRepo |
| |
119 | MRs []store.DashboardItem |
| |
120 | Issues []store.DashboardItem |
| |
121 | }{s.siteName(), viewer.Username, s.describeAll(visible), mrs, issues}) |
| |
122 | } |
| |
123 | |
| |
124 | func (s *Server) explore(w http.ResponseWriter, r *http.Request) { |
| 87 | repos, err := s.st.ListPublicRepos() |
125 | repos, err := s.st.ListPublicRepos() |
| 88 | if err != nil { |
126 | if err != nil { |
| 89 | http.Error(w, "internal error", http.StatusInternalServerError) |
127 | http.Error(w, "internal error", http.StatusInternalServerError) |
| 90 | return |
128 | return |
| 91 | } |
129 | } |
| 92 | var viewer store.User |
130 | var viewer store.User |
| 93 | var mine []store.Repo |
| |
| 94 | if s.cfg.Web.Mode == "accounts" { |
131 | if s.cfg.Web.Mode == "accounts" { |
| 95 | if viewer = s.viewer(r); viewer.ID != 0 { |
132 | viewer = s.viewer(r) |
| 96 | all, err := s.st.ListReposForUser(viewer.ID) |
| |
| 97 | if err == nil { |
| |
| 98 | for _, rp := range all { |
| |
| 99 | if rp.Visibility == "private" { |
| |
| 100 | mine = append(mine, rp) |
| |
| 101 | } |
| |
| 102 | } |
| |
| 103 | } |
| |
| 104 | } |
| |
| 105 | } |
133 | } |
| 106 | q := strings.TrimSpace(r.URL.Query().Get("q")) |
134 | q := strings.TrimSpace(r.URL.Query().Get("q")) |
| 107 | s.render(w, "index.html", struct { |
135 | s.render(w, "explore.html", struct { |
| 108 | Site string |
136 | Site string |
| 109 | Viewer string |
137 | Viewer string |
| 110 | Query string |
138 | Query string |
| 111 | Repos []describedRepo |
139 | Repos []describedRepo |
| 112 | Mine []describedRepo |
140 | }{s.siteName(), viewer.Username, q, s.filterRepos(q, s.describeAll(repos))}) |
| 113 | }{s.siteName(), viewer.Username, q, |
| |
| 114 | s.filterRepos(q, s.describeAll(repos)), s.filterRepos(q, s.describeAll(mine))}) |
| |
| 115 | } |
141 | } |
| 116 | |
142 | |
| 117 | // filterRepos keeps repos whose path, description, or topics contain the |
143 | // filterRepos keeps repos whose path, description, or topics contain the |
| @@ -1041,6 +1067,21 @@ func (s *Server) mr(w http.ResponseWriter, r *http.Request) { |
| 1041 | md := s.ugcFor(r, p.Repo) |
1067 | md := s.ugcFor(r, p.Repo) |
| 1042 | var detachedThreads []diffThread |
1068 | var detachedThreads []diffThread |
| 1043 | lines, detachedThreads = attachThreads(lines, diffComments, m.HeadSHA, md) |
1069 | lines, detachedThreads = attachThreads(lines, diffComments, m.HeadSHA, md) |
| |
1070 | type diffStat struct{ Files, Adds, Dels int } |
| |
1071 | var stat diffStat |
| |
1072 | seenFiles := map[string]bool{} |
| |
1073 | for _, l := range lines { |
| |
1074 | switch l.Class { |
| |
1075 | case "add": |
| |
1076 | stat.Adds++ |
| |
1077 | case "del": |
| |
1078 | stat.Dels++ |
| |
1079 | } |
| |
1080 | if l.Path != "" && !seenFiles[l.Path] { |
| |
1081 | seenFiles[l.Path] = true |
| |
1082 | stat.Files++ |
| |
1083 | } |
| |
1084 | } |
| 1044 | s.render(w, "mr.html", struct { |
1085 | s.render(w, "mr.html", struct { |
| 1045 | repoPage |
1086 | repoPage |
| 1046 | MR store.MR |
1087 | MR store.MR |
| @@ -1050,8 +1091,9 @@ func (s *Server) mr(w http.ResponseWriter, r *http.Request) { |
| 1050 | Comments []renderedComment |
1091 | Comments []renderedComment |
| 1051 | Reviews []store.MRReview |
1092 | Reviews []store.MRReview |
| 1052 | DiffLines []diffLine |
1093 | DiffLines []diffLine |
| |
1094 | Stat diffStat |
| 1053 | DetachedThreads []diffThread |
1095 | DetachedThreads []diffThread |
| 1054 | }{p, m, md(m.Body), checks, store.CombinedStatus(checks), renderComments(comments, md), reviews, lines, detachedThreads}) |
1096 | }{p, m, md(m.Body), checks, store.CombinedStatus(checks), renderComments(comments, md), reviews, lines, stat, detachedThreads}) |
| 1055 | } |
1097 | } |
| 1056 | |
1098 | |
| 1057 | func (s *Server) refs(w http.ResponseWriter, r *http.Request) { |
1099 | func (s *Server) refs(w http.ResponseWriter, r *http.Request) { |