@@ -6,17 +6,13 @@ import ( "net/http" "net/url" "os" "sort" "dmitri.shuralyov.com/app/changes/component" "dmitri.shuralyov.com/service/change" "github.com/shurcooL/htmlg" "github.com/shurcooL/httperror" "github.com/shurcooL/octicon" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) var changesHTML = template.Must(template.New("").Parse(`{{define "Header"}}<html lang="en"> <head> {{.AnalyticsHTML}} <title>{{with .PageName}}{{.}} - {{end}}Go Changes</title> @@ -91,64 +87,25 @@ func (h *handler) serveChanges(w http.ResponseWriter, req *http.Request, pkg str cs = ic.ClosedChanges case filter == change.FilterAll: cs = append(ic.OpenChanges, ic.ClosedChanges...) // TODO: Measure if slow, optimize if needed. sort.Slice(cs, func(i, j int) bool { return cs[i].ID > cs[j].ID }) } openCount := uint64(len(ic.OpenChanges)) closedCount := uint64(len(ic.ClosedChanges)) w.Header().Set("Content-Type", "text/html; charset=utf-8") err = h.executeTemplate(w, req, "Header", map[string]interface{}{ "PageName": pkg, "AnalyticsHTML": h.analyticsHTML, }) if err != nil { return err } heading := htmlg.NodeComponent{ Type: html.ElementNode, Data: atom.H2.String(), Attr: []html.Attribute{{Key: atom.Style.String(), Val: "margin-top: 30px;"}}, FirstChild: htmlg.Text(pkg), } if pkg == otherPackages { heading.Data, heading.FirstChild = atom.H3.String(), htmlg.Text("Other Go Issues/Changes") } tabnav := tabnav{ Tabs: []tab{ { Content: contentCounter{ Content: iconText{Icon: octicon.IssueOpened, Text: "Issues"}, Count: len(ic.OpenIssues), }, URL: h.rtr.IssuesURL(pkg), }, { Content: contentCounter{ Content: iconText{Icon: octicon.GitPullRequest, Text: "Changes"}, Count: len(ic.OpenChanges), }, URL: h.rtr.ChangesURL(pkg), Selected: true, }, }, } var es []component.ChangeEntry for _, c := range cs { es = append(es, component.ChangeEntry{Change: c, BaseURI: "https://golang.org/cl"}) } changes := component.Changes{ ChangesNav: component.ChangesNav{ OpenCount: openCount, ClosedCount: closedCount, Path: req.URL.Path, Query: req.URL.Query(), StateQueryKey: stateQueryKey, }, Filter: filter, Entries: es, } err = htmlg.RenderComponents(w, heading, subheading{pkg}, tabnav, changes) err = htmlg.RenderComponents(w, heading{Pkg: pkg}, subheading{Pkg: pkg}, renderTabnav(changesTab, len(ic.OpenIssues), len(ic.OpenChanges), pkg, h.rtr), renderChanges(cs, len(ic.OpenChanges), len(ic.ClosedChanges), req.URL, filter), ) if err != nil { return err } err = h.executeTemplate(w, req, "Trailer", nil) return err
@@ -9,14 +9,10 @@ import ( "sort" "github.com/shurcooL/htmlg" "github.com/shurcooL/httperror" "github.com/shurcooL/issues" "github.com/shurcooL/issuesapp/component" "github.com/shurcooL/octicon" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) var issuesHTML = template.Must(template.New("").Parse(`{{define "Header"}}<html lang="en"> <head> {{.AnalyticsHTML}} <title>{{with .PageName}}{{.}} - {{end}}Go Issues</title> @@ -91,88 +87,33 @@ func (h *handler) serveIssues(w http.ResponseWriter, req *http.Request, pkg stri is = ic.ClosedIssues case filter == issues.AllStates: is = append(ic.OpenIssues, ic.ClosedIssues...) // TODO: Measure if slow, optimize if needed. sort.Slice(is, func(i, j int) bool { return is[i].ID > is[j].ID }) } openCount := uint64(len(ic.OpenIssues)) closedCount := uint64(len(ic.ClosedIssues)) w.Header().Set("Content-Type", "text/html; charset=utf-8") err = h.executeTemplate(w, req, "Header", map[string]interface{}{ "PageName": pkg, "AnalyticsHTML": h.analyticsHTML, }) if err != nil { return err } heading := htmlg.NodeComponent{ Type: html.ElementNode, Data: atom.H2.String(), Attr: []html.Attribute{{Key: atom.Style.String(), Val: "margin-top: 30px;"}}, FirstChild: htmlg.Text(pkg), } if pkg == otherPackages { heading.Data, heading.FirstChild = atom.H3.String(), htmlg.Text("Other Go Issues/Changes") } tabnav := tabnav{ Tabs: []tab{ { Content: contentCounter{ Content: iconText{Icon: octicon.IssueOpened, Text: "Issues"}, Count: len(ic.OpenIssues), }, URL: h.rtr.IssuesURL(pkg), Selected: true, }, { Content: contentCounter{ Content: iconText{Icon: octicon.GitPullRequest, Text: "Changes"}, Count: len(ic.OpenChanges), }, URL: h.rtr.ChangesURL(pkg), }, }, } title := ImportPathToFullPrefix(pkg) newIssue := htmlg.NodeComponent{ Type: html.ElementNode, Data: atom.Div.String(), Attr: []html.Attribute{{Key: atom.Style.String(), Val: "text-align: right;"}}, FirstChild: htmlg.A("New Issue", "https://golang.org/issue/new?title="+url.QueryEscape(title)), } var es []component.IssueEntry for _, i := range is { es = append(es, component.IssueEntry{Issue: i, BaseURI: "https://golang.org/issue"}) } issues := component.Issues{ IssuesNav: component.IssuesNav{ OpenCount: openCount, ClosedCount: closedCount, Path: req.URL.Path, Query: req.URL.Query(), StateQueryKey: stateQueryKey, }, Filter: filter, Entries: es, } err = htmlg.RenderComponents(w, heading, subheading{pkg}, tabnav, newIssue, issues) err = htmlg.RenderComponents(w, heading{Pkg: pkg}, subheading{Pkg: pkg}, renderTabnav(issuesTab, len(ic.OpenIssues), len(ic.OpenChanges), pkg, h.rtr), renderNewIssue(pkg), renderIssues(is, len(ic.OpenIssues), len(ic.ClosedIssues), req.URL, filter), ) if err != nil { return err } err = h.executeTemplate(w, req, "Trailer", nil) return err } type subheading struct{ Pkg string } func (s subheading) Render() []*html.Node { switch s.Pkg { case otherPackages: return []*html.Node{htmlg.P(htmlg.Text("Issues and changes that don't fit into any existing Go package."))} default: return nil } } const ( // stateQueryKey is name of query key for controlling issue/change state filter. stateQueryKey = "state" )
@@ -0,0 +1,122 @@ package main import ( "net/url" changescomponent "dmitri.shuralyov.com/app/changes/component" "dmitri.shuralyov.com/service/change" "github.com/shurcooL/htmlg" "github.com/shurcooL/issues" issuescomponent "github.com/shurcooL/issuesapp/component" "github.com/shurcooL/octicon" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) type heading struct{ Pkg string } func (h heading) Render() []*html.Node { switch h.Pkg { default: h2 := &html.Node{ Type: html.ElementNode, Data: atom.H2.String(), Attr: []html.Attribute{{Key: atom.Style.String(), Val: "margin-top: 30px;"}}, FirstChild: htmlg.Text(h.Pkg), } return []*html.Node{h2} case otherPackages: h3 := &html.Node{ Type: html.ElementNode, Data: atom.H3.String(), Attr: []html.Attribute{{Key: atom.Style.String(), Val: "margin-top: 30px;"}}, FirstChild: htmlg.Text("Other Go Issues/Changes"), } return []*html.Node{h3} } } type subheading struct{ Pkg string } func (s subheading) Render() []*html.Node { switch s.Pkg { case otherPackages: return []*html.Node{htmlg.P(htmlg.Text("Issues and changes that don't fit into any existing Go package."))} default: return nil } } func renderTabnav(selected pageTab, openIssues, openChanges int, pattern string, rtr Router) htmlg.Component { return tabnav{ Tabs: []tab{ { Content: contentCounter{ Content: iconText{Icon: octicon.IssueOpened, Text: "Issues"}, Count: openIssues, }, URL: rtr.IssuesURL(pattern), Selected: selected == issuesTab, }, { Content: contentCounter{ Content: iconText{Icon: octicon.GitPullRequest, Text: "Changes"}, Count: openChanges, }, URL: rtr.ChangesURL(pattern), Selected: selected == changesTab, }, }, } } type pageTab uint8 const ( _ pageTab = iota issuesTab changesTab ) func renderNewIssue(pkg string) htmlg.Component { title := ImportPathToFullPrefix(pkg) return htmlg.NodeComponent{ Type: html.ElementNode, Data: atom.Div.String(), Attr: []html.Attribute{{Key: atom.Style.String(), Val: "text-align: right;"}}, FirstChild: htmlg.A("New Issue", "https://golang.org/issue/new?title="+url.QueryEscape(title)), } } func renderIssues(is []issues.Issue, openIssues, closedIssues int, reqURL *url.URL, filter issues.StateFilter) htmlg.Component { var es []issuescomponent.IssueEntry for _, i := range is { es = append(es, issuescomponent.IssueEntry{Issue: i, BaseURI: "https://golang.org/issue"}) } return issuescomponent.Issues{ IssuesNav: issuescomponent.IssuesNav{ OpenCount: uint64(openIssues), ClosedCount: uint64(closedIssues), Path: reqURL.Path, Query: reqURL.Query(), StateQueryKey: stateQueryKey, }, Filter: filter, Entries: es, } } func renderChanges(cs []change.Change, openChanges, closedChanges int, reqURL *url.URL, filter change.StateFilter) htmlg.Component { var es []changescomponent.ChangeEntry for _, c := range cs { es = append(es, changescomponent.ChangeEntry{Change: c, BaseURI: "https://golang.org/cl"}) } return changescomponent.Changes{ ChangesNav: changescomponent.ChangesNav{ OpenCount: uint64(openChanges), ClosedCount: uint64(closedChanges), Path: reqURL.Path, Query: reqURL.Query(), StateQueryKey: stateQueryKey, }, Filter: filter, Entries: es, } }