@@ -1,6 +1,6 @@
package changesapp
package changes
import (
"bytes"
"context"
"encoding/json"
@@ -15,14 +15,14 @@ import (
"sort"
"strconv"
"strings"
"time"
"dmitri.shuralyov.com/changes"
"dmitri.shuralyov.com/changes/app/assets"
"dmitri.shuralyov.com/changes/app/common"
"dmitri.shuralyov.com/changes/app/component"
"dmitri.shuralyov.com/app/changes/assets"
"dmitri.shuralyov.com/app/changes/common"
"dmitri.shuralyov.com/app/changes/component"
"dmitri.shuralyov.com/service/change"
"github.com/dustin/go-humanize"
"github.com/shurcooL/github_flavored_markdown"
"github.com/shurcooL/htmlg"
"github.com/shurcooL/httperror"
"github.com/shurcooL/httpfs/html/vfstemplate"
@@ -34,11 +34,11 @@ import (
"github.com/shurcooL/users"
"golang.org/x/net/html"
"sourcegraph.com/sourcegraph/go-diff/diff"
)
// TODO: Find a better way for changesapp to be able to ensure registration of a top-level route:
// TODO: Find a better way for changes to be able to ensure registration of a top-level route:
//
// emojisHandler := httpgzip.FileServer(emojis.Assets, httpgzip.FileServerOptions{ServeError: httpgzip.Detailed})
// http.Handle("/emojis/", http.StripPrefix("/emojis", emojisHandler))
//
// So that it can depend on it.
@@ -50,18 +50,18 @@ import (
//
// In order to serve HTTP requests, the returned http.Handler expects each incoming
// request to have 2 parameters provided to it via RepoSpecContextKey and BaseURIContextKey
// context keys. For example:
//
// changesApp := changesapp.New(...)
// changesApp := changes.New(...)
//
// http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
// req = req.WithContext(context.WithValue(req.Context(), changesapp.RepoSpecContextKey, string(...)))
// req = req.WithContext(context.WithValue(req.Context(), changesapp.BaseURIContextKey, string(...)))
// req = req.WithContext(context.WithValue(req.Context(), changes.RepoSpecContextKey, string(...)))
// req = req.WithContext(context.WithValue(req.Context(), changes.BaseURIContextKey, string(...)))
// changesApp.ServeHTTP(w, req)
// })
func New(service changes.Service, users users.Service, opt Options) http.Handler {
func New(service change.Service, users users.Service, opt Options) http.Handler {
static, err := loadTemplates(common.State{}, opt.BodyPre)
if err != nil {
log.Fatalln("loadTemplates failed:", err)
}
h := handler{
@@ -98,14 +98,14 @@ type Options struct {
// BodyTop provides components to include on top of <body> of page rendered for req. It can be nil.
BodyTop func(*http.Request, common.State) ([]htmlg.Component, error)
}
// handler handles all requests to changesapp. It acts like a request multiplexer,
// handler handles all requests to changes. It acts like a request multiplexer,
// choosing from various endpoints and parsing the repository ID from URL.
type handler struct {
is changes.Service
is change.Service
us users.Service // May be nil if there's no users service.
assetsFileServer http.Handler
gfmFileServer http.Handler
@@ -115,14 +115,14 @@ type handler struct {
Options
}
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) error {
if _, ok := req.Context().Value(RepoSpecContextKey).(string); !ok {
return fmt.Errorf("request to %v doesn't have changesapp.RepoSpecContextKey context key set", req.URL.Path)
return fmt.Errorf("request to %v doesn't have changes.RepoSpecContextKey context key set", req.URL.Path)
}
if _, ok := req.Context().Value(BaseURIContextKey).(string); !ok {
return fmt.Errorf("request to %v doesn't have changesapp.BaseURIContextKey context key set", req.URL.Path)
return fmt.Errorf("request to %v doesn't have changes.BaseURIContextKey context key set", req.URL.Path)
}
// Handle "/assets/gfm/...".
if strings.HasPrefix(req.URL.Path, "/assets/gfm/") {
req = stripPrefix(req, len("/assets/gfm"))
@@ -192,19 +192,19 @@ func (h *handler) ChangesHandler(w http.ResponseWriter, req *http.Request) error
}
filter, err := stateFilter(req.URL.Query())
if err != nil {
return httperror.BadRequest{Err: err}
}
is, err := h.is.List(req.Context(), state.RepoSpec, changes.ListOptions{Filter: filter})
is, err := h.is.List(req.Context(), state.RepoSpec, change.ListOptions{Filter: filter})
if err != nil {
return err
}
openCount, err := h.is.Count(req.Context(), state.RepoSpec, changes.ListOptions{Filter: changes.FilterOpen})
openCount, err := h.is.Count(req.Context(), state.RepoSpec, change.ListOptions{Filter: change.FilterOpen})
if err != nil {
return fmt.Errorf("changes.Count(open): %v", err)
}
closedCount, err := h.is.Count(req.Context(), state.RepoSpec, changes.ListOptions{Filter: changes.FilterClosedMerged})
closedCount, err := h.is.Count(req.Context(), state.RepoSpec, change.ListOptions{Filter: change.FilterClosedMerged})
if err != nil {
return fmt.Errorf("changes.Count(closed): %v", err)
}
var es []component.ChangeEntry
for _, i := range is {
@@ -235,25 +235,25 @@ const (
stateQueryKey = "state"
)
// stateFilter parses the change state filter from query,
// returning an error if the value is unsupported.
func stateFilter(query url.Values) (changes.StateFilter, error) {
func stateFilter(query url.Values) (change.StateFilter, error) {
selectedTabName := query.Get(stateQueryKey)
switch selectedTabName {
case "":
return changes.FilterOpen, nil
return change.FilterOpen, nil
case "closed":
return changes.FilterClosedMerged, nil
return change.FilterClosedMerged, nil
case "all":
return changes.FilterAll, nil
return change.FilterAll, nil
default:
return "", fmt.Errorf("unsupported state filter value: %q", selectedTabName)
}
}
func (s state) augmentUnread(ctx context.Context, es []component.ChangeEntry, is changes.Service, notificationsService notifications.Service) []component.ChangeEntry {
func (s state) augmentUnread(ctx context.Context, es []component.ChangeEntry, is change.Service, notificationsService notifications.Service) []component.ChangeEntry {
if notificationsService == nil {
return es
}
tt, ok := is.(interface {
@@ -307,23 +307,23 @@ func (h *handler) MockHandler(w http.ResponseWriter, req *http.Request) error {
return fmt.Errorf("loadTemplates: %v", err)
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
err = t.ExecuteTemplate(w, "review-mock", struct {
state
Review changes.Review
Review change.Review
}{
state: st,
Review: changes.Review{
Review: change.Review{
ID: 0,
User: users.User{Login: "Eric Grosse", AvatarURL: "https://lh6.googleusercontent.com/-_sdEtv2PRxk/AAAAAAAAAAI/AAAAAAAAAAA/aE1Q66Cuvb4/s100-p/photo.jpg"},
CreatedAt: time.Now().UTC(),
Edited: nil,
State: changes.Approved,
State: change.Approved,
Body: "",
Reactions: []reactions.Reaction{},
Editable: true,
Comments: []changes.InlineComment{
Comments: []change.InlineComment{
{
File: "rpc/keyserver/server.go",
Line: 26,
Body: "Ok by me, but how was this chosen?",
},
@@ -446,13 +446,13 @@ func (h *handler) ChangeFilesHandler(w http.ResponseWriter, req *http.Request, c
}
if next := i + 1; next < len(cs) {
commit.NextSHA = cs[next].SHA
}
}
var opt *changes.GetDiffOptions
var opt *change.GetDiffOptions
if commitID != "" {
opt = &changes.GetDiffOptions{Commit: commitID}
opt = &change.GetDiffOptions{Commit: commitID}
}
rawDiff, err := h.is.GetDiff(req.Context(), state.RepoSpec, state.ChangeID, opt)
if err != nil {
return err
}
@@ -481,11 +481,11 @@ func (h *handler) ChangeFilesHandler(w http.ResponseWriter, req *http.Request, c
return err
}
// commitIndex returns the index of commit with SHA equal to commitID,
// or -1 if not found.
func commitIndex(cs []changes.Commit, commitID string) int {
func commitIndex(cs []change.Commit, commitID string) int {
for i := range cs {
if cs[i].SHA == commitID {
return i
}
}
@@ -543,11 +543,11 @@ type state struct {
BodyTop template.HTML
common.State
Changes component.Changes
Change changes.Change
Change change.Change
Timeline []timelineItem
}
func (s state) Tabnav(selected string) template.HTML {
// Render the tabnav.
@@ -622,12 +622,12 @@ func loadTemplates(state common.State, bodyPre string) (*template.Template, erro
},
"render": func(c htmlg.Component) template.HTML {
return template.HTML(htmlg.Render(c.Render()...))
},
"event": func(e changes.TimelineItem) htmlg.Component { return component.Event{Event: e} },
"changeStateBadge": func(c changes.Change) htmlg.Component { return component.ChangeStateBadge{Change: c} },
"event": func(e change.TimelineItem) htmlg.Component { return component.Event{Event: e} },
"changeStateBadge": func(c change.Change) htmlg.Component { return component.ChangeStateBadge{Change: c} },
"time": func(t time.Time) htmlg.Component { return component.Time{Time: t} },
"user": func(u users.User) htmlg.Component { return component.User{User: u} },
"avatar": func(u users.User) htmlg.Component { return component.Avatar{User: u, Size: 48} },
})
t, err := vfstemplate.ParseGlob(assets.Assets, t, "/assets/*.tmpl")
@@ -642,11 +642,11 @@ func loadTemplates(state common.State, bodyPre string) (*template.Template, erro
type contextKey struct {
name string
}
func (k *contextKey) String() string {
return "dmitri.shuralyov.com/changes/app context value " + k.name
return "dmitri.shuralyov.com/app/changes context value " + k.name
}
// stripPrefix returns request r with prefix of length prefixLen stripped from r.URL.Path.
// prefixLen must not be longer than len(r.URL.Path), otherwise stripPrefix panics.
// If r.URL.Path is empty after the prefix is stripped, the path is changed to "/".