diff --git a/cmd/internal/pkgsite/server.go b/cmd/internal/pkgsite/server.go
index 32986a5..7e7c6a0 100644
--- a/cmd/internal/pkgsite/server.go
+++ b/cmd/internal/pkgsite/server.go
@@ -10,10 +10,12 @@
"encoding/json"
"fmt"
"io/fs"
+ "maps"
"net/http"
"os"
"os/exec"
"path/filepath"
+ "slices"
"sort"
"strings"
"time"
@@ -101,21 +103,30 @@
// Collect unique module Paths served by this server.
seenModules := make(map[frontend.LocalModule]bool)
- var allModules []frontend.LocalModule
+ var localModules []frontend.LocalModule
for _, modules := range cfg.dirs {
for _, m := range modules {
if seenModules[m] {
continue
}
seenModules[m] = true
- allModules = append(allModules, m)
+ localModules = append(localModules, m)
}
}
- sort.Slice(allModules, func(i, j int) bool {
- return allModules[i].ModulePath < allModules[j].ModulePath
+ sort.Slice(localModules, func(i, j int) bool {
+ return localModules[i].ModulePath < localModules[j].ModulePath
})
- return newServer(getters, allModules, cfg.proxy, serverCfg.GoDocMode, serverCfg.DevMode, serverCfg.DevModeStaticDir)
+ // Collect dependencies.
+ var directDeps, indirectDeps []frontend.LocalModule
+ if !serverCfg.GOPATHMode && serverCfg.UseListedMods {
+ directDeps, indirectDeps, err = getModuleDependencies(serverCfg.Paths, localModules)
+ if err != nil {
+ return nil, fmt.Errorf("getting module dependencies: %v", err)
+ }
+ }
+
+ return newServer(getters, localModules, directDeps, indirectDeps, cfg.proxy, serverCfg.GoDocMode, serverCfg.DevMode, serverCfg.DevModeStaticDir)
}
// getModuleDirs returns the set of workspace modules for each directory,
@@ -157,6 +168,62 @@
return dirModules, nil
}
+// getModuleDependencies returns all dependencies for the given directory,
+// determined by running "go list -m -json all".
+//
+// It returns two slices: direct dependencies (Indirect=false) and indirect
+// dependencies (Indirect=true). Modules without a Dir field (unreachable
+// modules) are filtered out. The main module itself is also excluded.
+func getModuleDependencies(dirs []string, localModules []frontend.LocalModule) (direct, indirect []frontend.LocalModule, err error) {
+ directMods, indirectMods := make(map[frontend.LocalModule]bool), make(map[frontend.LocalModule]bool)
+ mainModulePaths := make(map[string]bool)
+ for _, lm := range localModules {
+ mainModulePaths[lm.ModulePath] = true
+ }
+ for _, dir := range dirs {
+ output, err := runGo(dir, "list", "-m", "-json", "all")
+ if err != nil {
+ return nil, nil, fmt.Errorf("listing all modules in %s: %v", dir, err)
+ }
+ decoder := json.NewDecoder(bytes.NewBuffer(output))
+ for decoder.More() {
+ var m struct {
+ ModulePath string `json:"Path"`
+ Dir string `json:"Dir"`
+ Indirect bool `json:"Indirect"`
+ }
+ if err := decoder.Decode(&m); err != nil {
+ return nil, nil, err
+ }
+ // Skip modules without a Dir (not downloaded/unreachable)
+ if m.Dir == "" {
+ continue
+ }
+ // Skip the main module(s) themselves and special modules
+ if mainModulePaths[m.ModulePath] {
+ continue
+ }
+ if m.ModulePath == "std" || m.ModulePath == "command-line-arguments" {
+ continue
+ }
+ if m.Indirect {
+ mm := frontend.LocalModule{ModulePath: m.ModulePath, Dir: m.Dir}
+ indirectMods[mm] = true
+ } else {
+ mm := frontend.LocalModule{ModulePath: m.ModulePath, Dir: m.Dir}
+ directMods[mm] = true
+ }
+ }
+ }
+ direct = slices.SortedFunc(maps.Keys(directMods), func(a, b frontend.LocalModule) int {
+ return strings.Compare(a.ModulePath, b.ModulePath)
+ })
+ indirect = slices.SortedFunc(maps.Keys(indirectMods), func(a, b frontend.LocalModule) int {
+ return strings.Compare(a.ModulePath, b.ModulePath)
+ })
+ return direct, indirect, nil
+}
+
// getGOPATHModuleDirs returns local module information for directories in
// GOPATH corresponding to the requested module Paths.
//
@@ -276,7 +343,7 @@
return getters, nil
}
-func newServer(getters []fetch.ModuleGetter, localModules []frontend.LocalModule, prox *proxy.Client, goDocMode bool, devMode bool, staticFlag string) (*frontend.Server, error) {
+func newServer(getters []fetch.ModuleGetter, localModules, directDeps, indirectDeps []frontend.LocalModule, prox *proxy.Client, goDocMode bool, devMode bool, staticFlag string) (*frontend.Server, error) {
lds := fetchdatasource.Options{
Getters: getters,
ProxyClientForLatest: prox,
@@ -299,14 +366,16 @@
go lds.GetUnitMeta(context.Background(), "", "std", "latest")
server, err := frontend.NewServer(frontend.ServerConfig{
- DataSourceGetter: func(context.Context) internal.DataSource { return lds },
- TemplateFS: template.TrustedFSFromEmbed(static.FS),
- StaticFS: staticFS,
- DevMode: devMode,
- GoDocMode: goDocMode,
- LocalMode: true,
- LocalModules: localModules,
- ThirdPartyFS: thirdparty.FS,
+ DataSourceGetter: func(context.Context) internal.DataSource { return lds },
+ TemplateFS: template.TrustedFSFromEmbed(static.FS),
+ StaticFS: staticFS,
+ DevMode: devMode,
+ GoDocMode: goDocMode,
+ LocalMode: true,
+ LocalModules: localModules,
+ DirectDependencies: directDeps,
+ IndirectDependencies: indirectDeps,
+ ThirdPartyFS: thirdparty.FS,
})
if err != nil {
return nil, err
diff --git a/cmd/internal/pkgsite/server_test.go b/cmd/internal/pkgsite/server_test.go
index 49d0921..59ae9e4 100644
--- a/cmd/internal/pkgsite/server_test.go
+++ b/cmd/internal/pkgsite/server_test.go
@@ -51,6 +51,28 @@
-- a.go --
package a
`)
+
+ // Create a module with a dependency for testing the homepage dependencies section.
+ // Both modules are in subdirectories so we can use a relative replace directive.
+ localModuleWithDep, _ := testhelper.WriteTxtarToTempDir(t, `
+-- main/go.mod --
+module example.com/withdep
+
+require example.com/dep v0.0.0
+
+replace example.com/dep => ../dep
+-- main/main.go --
+package main
+
+import _ "example.com/dep"
+-- dep/go.mod --
+module example.com/dep
+-- dep/dep.go --
+package dep
+
+var X = 1
+`)
+ localModuleWithDep = filepath.Join(localModuleWithDep, "main")
cacheDir := repoPath("internal/fetch/testdata/modcache")
testModules := proxytest.LoadTestModules(repoPath("internal/proxy/testdata"))
prox, teardown := proxytest.SetupTestClient(t, testModules)
@@ -190,6 +212,30 @@
http.StatusFailedDependency,
hasText("page is not supported"),
},
+ {
+ "homepage local modules",
+ cfg(func(c *ServerConfig) {
+ c.UseLocalStdlib = false
+ }),
+ "",
+ http.StatusOK,
+ in("",
+ in(".Homepage-mainModule", hasText("example.com/testmod")),
+ in(".Homepage-stdlib a", hasText("Browse Standard Library"))),
+ },
+ {
+ "homepage with dependencies",
+ cfg(func(c *ServerConfig) {
+ c.Paths = []string{localModuleWithDep}
+ c.UseLocalStdlib = false
+ }),
+ "",
+ http.StatusOK,
+ in("",
+ in(".Homepage-mainModule", hasText("example.com/withdep")),
+ in(".Homepage-dependencies", hasText("example.com/dep")),
+ in(".Homepage-stdlib a", hasText("Browse Standard Library"))),
+ },
// TODO(rfindley): add a test for the standard library once it doesn't go
// through the stdlib package.
// See also golang/go#58923.
diff --git a/internal/frontend/homepage.go b/internal/frontend/homepage.go
index 1ec5a72..15de91b 100644
--- a/internal/frontend/homepage.go
+++ b/internal/frontend/homepage.go
@@ -51,6 +51,14 @@
// LocalModules holds locally-hosted modules, for quick navigation.
// Empty in production.
LocalModules []LocalModule
+
+ // DirectDependencies holds direct dependencies of the local module(s).
+ // Empty in production.
+ DirectDependencies []LocalModule
+
+ // IndirectDependencies holds indirect (transitive) dependencies.
+ // Empty in production.
+ IndirectDependencies []LocalModule
}
// LocalModule holds information about a locally-hosted module.
@@ -63,9 +71,11 @@
func (s *Server) serveHomepage(ctx context.Context, w http.ResponseWriter, r *http.Request) {
s.servePage(ctx, w, "homepage", Homepage{
- BasePage: s.newBasePage(r, "Go Packages"),
- SearchTips: searchTips,
- TipIndex: rand.Intn(len(searchTips)),
- LocalModules: s.localModules,
+ BasePage: s.newBasePage(r, "Go Packages"),
+ SearchTips: searchTips,
+ TipIndex: rand.Intn(len(searchTips)),
+ LocalModules: s.localModules,
+ DirectDependencies: s.directDependencies,
+ IndirectDependencies: s.indirectDependencies,
})
}
diff --git a/internal/frontend/server.go b/internal/frontend/server.go
index 9648c5e..97b6ad9 100644
--- a/internal/frontend/server.go
+++ b/internal/frontend/server.go
@@ -51,6 +51,8 @@
goDocMode bool // running to serve documentation for 'go doc'
localMode bool // running locally (i.e. ./cmd/pkgsite)
localModules []LocalModule // locally hosted modules; empty in production
+ directDependencies []LocalModule // direct dependencies; empty in production
+ indirectDependencies []LocalModule // indirect dependencies; empty in production
errorPage []byte
appVersionLabel string
googleTagManagerID string
@@ -96,6 +98,8 @@
LocalMode bool
GoDocMode bool
LocalModules []LocalModule
+ DirectDependencies []LocalModule
+ IndirectDependencies []LocalModule
Reporter derrors.Reporter
VulndbClient *vuln.Client
HTTPClient *http.Client
@@ -121,6 +125,8 @@
localMode: scfg.LocalMode,
goDocMode: scfg.GoDocMode,
localModules: scfg.LocalModules,
+ directDependencies: scfg.DirectDependencies,
+ indirectDependencies: scfg.IndirectDependencies,
templates: ts,
reporter: scfg.Reporter,
fileMux: http.NewServeMux(),
diff --git a/static/frontend/homepage/homepage.css b/static/frontend/homepage/homepage.css
index 5961756..ab41c62 100644
--- a/static/frontend/homepage/homepage.css
+++ b/static/frontend/homepage/homepage.css
@@ -150,25 +150,88 @@
width: 1rem;
}
-.Homepage-modules {
- margin: auto;
- max-width: 45.0625rem;
- width: 100%;
+/* Local mode homepage styles */
+.Homepage-local {
+ max-width: 60rem;
+ margin: 0 auto;
+ padding: 1.5rem;
}
-.Homepage-modules-header {
- color: var(--color-text);
- font-weight: bold;
+.Homepage-search--local {
+ margin: 1rem auto 2rem;
}
-.Homepage-modules ul {
- list-style: circle;
- padding: 0 1.5rem;
+.Homepage-localSection {
+ margin-bottom: 2rem;
}
-.Homepage-modules ul > li {
- font-size: 1rem;
- line-height: 1.75rem;
+.Homepage-localSection h2 {
+ font-size: 1.125rem;
+ margin: 0 0 0.75rem;
+ padding-bottom: 0.5rem;
+ border-bottom: var(--border);
+}
+
+.Homepage-localSection ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.Homepage-localSection li {
+ padding: 0.25rem 0;
+}
+
+.Homepage-mainModule ul {
+ background: var(--color-background-accented);
+ border-radius: var(--border-radius);
+ padding: 1rem;
+}
+
+.Homepage-mainModule li {
+ padding: 0.5rem 0;
+}
+
+.Homepage-mainModule a {
+ font-size: 1.125rem;
+ font-weight: 500;
+}
+
+.Homepage-mainModule small {
+ color: var(--color-text-subtle);
+ display: block;
+ font-size: 0.8125rem;
+ margin-top: 0.25rem;
+}
+
+.Homepage-mainModule {
+ margin-bottom: 2.5rem;
+}
+
+.Homepage-dependencies {
+ margin-bottom: 0.5rem;
+}
+
+.Homepage-transitive {
+ margin: 0;
+}
+
+.Homepage-transitive summary {
+ cursor: pointer;
+ color: var(--color-text-subtle);
+ font-size: 0.9375rem;
+ padding-left: 0.25rem;
+}
+
+.Homepage-transitive[open] summary {
+ margin-bottom: 0.25rem;
+}
+
+.Homepage-stdlib {
+ text-align: center;
+ margin: 1rem 0 0;
+ padding-top: 0.75rem;
+ border-top: var(--border);
}
.Questions {
diff --git a/static/frontend/homepage/homepage.min.css b/static/frontend/homepage/homepage.min.css
index 755b4df..7a96a02 100644
--- a/static/frontend/homepage/homepage.min.css
+++ b/static/frontend/homepage/homepage.min.css
@@ -3,7 +3,7 @@
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
-.go-SearchForm{display:none}.Homepage-logo{border-radius:var(--border-radius);display:block;height:10rem;margin:3.125rem auto;width:auto}[data-theme=dark] .Homepage-logo{mix-blend-mode:difference}@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) .Homepage-logo{mix-blend-mode:difference}}@media only screen and (min-width: 52rem){.Homepage{margin:2rem auto}.Homepage-logo{margin:3.5rem auto}}.Homepage-search{--border-radius: .5rem;height:3rem;margin:2.5rem auto 0;max-width:45.0625rem;position:relative;width:100%}.Homepage-search:before{background:url(/static/shared/icon/search_gm_grey_24dp.svg) left no-repeat;content:"";height:3rem;left:.75rem;position:absolute;width:1.5rem;z-index:3}.Homepage-search .go-Select,.Homepage-search .go-Input{padding-left:2.5rem}.Homepage-search--symbol .go-Input{border-bottom-right-radius:var(--border-radius);border-top-right-radius:var(--border-radius);padding-left:2.5rem}.Homepage-search .go-Button{justify-content:center;width:7.375rem}.Homepage-search--symbol .go-Button{display:none}@media only screen and (min-width: 30rem){.Homepage-search--symbol .go-Input{border-bottom-right-radius:0;border-top-right-radius:0}.Homepage-search--symbol .go-Button{display:inline-flex}}input[type=search]::-webkit-search-decoration{display:none}.Homepage-tips{margin:auto;max-width:45.0625rem;width:100%}[data-local=true] .Homepage-tips{display:none}.Homepage-examples{align-items:center;display:flex;flex-direction:column;font-size:.875rem;gap:.5rem 1rem;justify-content:space-between;margin:0 auto;max-width:45.0625rem;white-space:nowrap;width:inherit}@media only screen and (min-width: 52rem){.Homepage-examples{flex-direction:row}}.Homepage-examplesTitle{color:var(--color-text-subtle);font-weight:500;text-transform:uppercase}.Homepage-examplesList{display:flex;flex-grow:1;flex-wrap:wrap;gap:.5rem 2rem}a.Homepage-helpLink{align-items:center;display:inline-flex;font-size:1em;font-weight:initial;margin-left:.5rem;white-space:nowrap}.Homepage-helpLink img{height:1rem;margin-left:.25rem;position:relative;top:.1875rem;width:1rem}.Homepage-modules{margin:auto;max-width:45.0625rem;width:100%}.Homepage-modules-header{color:var(--color-text);font-weight:700}.Homepage-modules ul{list-style:circle;padding:0 1.5rem}.Homepage-modules ul>li{font-size:1rem;line-height:1.75rem}.Questions{background:var(--color-background-accented);color:var(--color-text);display:flex;padding-bottom:1rem;padding-top:.5rem}.Questions-header{color:var(--color-text);font-weight:700;margin:1rem 0}.Questions-content{flex-grow:1;margin:0 auto;max-width:75.75rem;padding:0 1.5rem}.Questions-content a{color:var(--color-bright-text-link)}.Questions-content ul{list-style:none;padding-inline-start:0}.Questions-content ul>li{font-size:.875rem;line-height:1.75rem}
+.go-SearchForm{display:none}.Homepage-logo{border-radius:var(--border-radius);display:block;height:10rem;margin:3.125rem auto;width:auto}[data-theme=dark] .Homepage-logo{mix-blend-mode:difference}@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) .Homepage-logo{mix-blend-mode:difference}}@media only screen and (min-width: 52rem){.Homepage{margin:2rem auto}.Homepage-logo{margin:3.5rem auto}}.Homepage-search{--border-radius: .5rem;height:3rem;margin:2.5rem auto 0;max-width:45.0625rem;position:relative;width:100%}.Homepage-search:before{background:url(/static/shared/icon/search_gm_grey_24dp.svg) left no-repeat;content:"";height:3rem;left:.75rem;position:absolute;width:1.5rem;z-index:3}.Homepage-search .go-Select,.Homepage-search .go-Input{padding-left:2.5rem}.Homepage-search--symbol .go-Input{border-bottom-right-radius:var(--border-radius);border-top-right-radius:var(--border-radius);padding-left:2.5rem}.Homepage-search .go-Button{justify-content:center;width:7.375rem}.Homepage-search--symbol .go-Button{display:none}@media only screen and (min-width: 30rem){.Homepage-search--symbol .go-Input{border-bottom-right-radius:0;border-top-right-radius:0}.Homepage-search--symbol .go-Button{display:inline-flex}}input[type=search]::-webkit-search-decoration{display:none}.Homepage-tips{margin:auto;max-width:45.0625rem;width:100%}[data-local=true] .Homepage-tips{display:none}.Homepage-examples{align-items:center;display:flex;flex-direction:column;font-size:.875rem;gap:.5rem 1rem;justify-content:space-between;margin:0 auto;max-width:45.0625rem;white-space:nowrap;width:inherit}@media only screen and (min-width: 52rem){.Homepage-examples{flex-direction:row}}.Homepage-examplesTitle{color:var(--color-text-subtle);font-weight:500;text-transform:uppercase}.Homepage-examplesList{display:flex;flex-grow:1;flex-wrap:wrap;gap:.5rem 2rem}a.Homepage-helpLink{align-items:center;display:inline-flex;font-size:1em;font-weight:initial;margin-left:.5rem;white-space:nowrap}.Homepage-helpLink img{height:1rem;margin-left:.25rem;position:relative;top:.1875rem;width:1rem}.Homepage-local{max-width:60rem;margin:0 auto;padding:1.5rem}.Homepage-search--local{margin:1rem auto 2rem}.Homepage-localSection{margin-bottom:2rem}.Homepage-localSection h2{font-size:1.125rem;margin:0 0 .75rem;padding-bottom:.5rem;border-bottom:var(--border)}.Homepage-localSection ul{list-style:none;margin:0;padding:0}.Homepage-localSection li{padding:.25rem 0}.Homepage-mainModule ul{background:var(--color-background-accented);border-radius:var(--border-radius);padding:1rem}.Homepage-mainModule li{padding:.5rem 0}.Homepage-mainModule a{font-size:1.125rem;font-weight:500}.Homepage-mainModule small{color:var(--color-text-subtle);display:block;font-size:.8125rem;margin-top:.25rem}.Homepage-mainModule{margin-bottom:2.5rem}.Homepage-dependencies{margin-bottom:.5rem}.Homepage-transitive{margin:0}.Homepage-transitive summary{cursor:pointer;color:var(--color-text-subtle);font-size:.9375rem;padding-left:.25rem}.Homepage-transitive[open] summary{margin-bottom:.25rem}.Homepage-stdlib{text-align:center;margin:1rem 0 0;padding-top:.75rem;border-top:var(--border)}.Questions{background:var(--color-background-accented);color:var(--color-text);display:flex;padding-bottom:1rem;padding-top:.5rem}.Questions-header{color:var(--color-text);font-weight:700;margin:1rem 0}.Questions-content{flex-grow:1;margin:0 auto;max-width:75.75rem;padding:0 1.5rem}.Questions-content a{color:var(--color-bright-text-link)}.Questions-content ul{list-style:none;padding-inline-start:0}.Questions-content ul>li{font-size:.875rem;line-height:1.75rem}
/*!
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
diff --git a/static/frontend/homepage/homepage.min.css.map b/static/frontend/homepage/homepage.min.css.map
index 031143e..5fb6209 100644
--- a/static/frontend/homepage/homepage.min.css.map
+++ b/static/frontend/homepage/homepage.min.css.map
@@ -1,7 +1,7 @@
{
"version": 3,
"sources": ["homepage.css"],
- "sourcesContent": ["/*!\n * Copyright 2020 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/* Hide the search form in the header. */\n.go-SearchForm {\n display: none;\n}\n\n.Homepage-logo {\n border-radius: var(--border-radius);\n display: block;\n height: 10rem;\n margin: 3.125rem auto;\n width: auto;\n}\n\n[data-theme='dark'] .Homepage-logo {\n mix-blend-mode: difference;\n}\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme='light']) .Homepage-logo {\n mix-blend-mode: difference;\n }\n}\n@media only screen and (min-width: 52rem) {\n .Homepage {\n margin: 2rem auto;\n }\n\n .Homepage-logo {\n margin: 3.5rem auto;\n }\n}\n\n.Homepage-search {\n --border-radius: 0.5rem;\n\n height: 3rem;\n margin: 2.5rem auto 0;\n max-width: 45.0625rem;\n position: relative;\n width: 100%;\n}\n\n.Homepage-search::before {\n background: url('/static/shared/icon/search_gm_grey_24dp.svg') left no-repeat;\n content: '';\n height: 3rem;\n left: 0.75rem;\n position: absolute;\n width: 1.5rem;\n z-index: 3;\n}\n\n.Homepage-search .go-Select {\n padding-left: 2.5rem;\n}\n\n.Homepage-search .go-Input {\n padding-left: 2.5rem;\n}\n\n.Homepage-search--symbol .go-Input {\n border-bottom-right-radius: var(--border-radius);\n border-top-right-radius: var(--border-radius);\n padding-left: 2.5rem;\n}\n\n.Homepage-search .go-Button {\n justify-content: center;\n width: 7.375rem;\n}\n\n.Homepage-search--symbol .go-Button {\n display: none;\n}\n@media only screen and (min-width: 30rem) {\n .Homepage-search--symbol .go-Input {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n }\n\n .Homepage-search--symbol .go-Button {\n display: inline-flex;\n }\n}\n\ninput[type='search']::-webkit-search-decoration {\n display: none;\n}\n\n.Homepage-tips {\n margin: auto;\n max-width: 45.0625rem;\n width: 100%;\n}\n\n[data-local='true'] .Homepage-tips {\n display: none;\n}\n\n.Homepage-examples {\n align-items: center;\n display: flex;\n flex-direction: column;\n font-size: 0.875rem;\n gap: 0.5rem 1rem;\n justify-content: space-between;\n margin: 0 auto;\n max-width: 45.0625rem;\n white-space: nowrap;\n width: inherit;\n}\n@media only screen and (min-width: 52rem) {\n .Homepage-examples {\n flex-direction: row;\n }\n}\n\n.Homepage-examplesTitle {\n color: var(--color-text-subtle);\n font-weight: 500;\n text-transform: uppercase;\n}\n\n.Homepage-examplesList {\n display: flex;\n flex-grow: 1;\n flex-wrap: wrap;\n gap: 0.5rem 2rem;\n}\n\na.Homepage-helpLink {\n align-items: center;\n display: inline-flex;\n font-size: 1em;\n font-weight: initial;\n margin-left: 0.5rem;\n white-space: nowrap;\n}\n\n.Homepage-helpLink img {\n height: 1rem;\n margin-left: 0.25rem;\n position: relative;\n top: 0.1875rem;\n width: 1rem;\n}\n\n.Homepage-modules {\n margin: auto;\n max-width: 45.0625rem;\n width: 100%;\n}\n\n.Homepage-modules-header {\n color: var(--color-text);\n font-weight: bold;\n}\n\n.Homepage-modules ul {\n list-style: circle;\n padding: 0 1.5rem;\n}\n\n.Homepage-modules ul > li {\n font-size: 1rem;\n line-height: 1.75rem;\n}\n\n.Questions {\n background: var(--color-background-accented);\n color: var(--color-text);\n display: flex;\n padding-bottom: 1rem;\n padding-top: 0.5rem;\n}\n\n.Questions-header {\n color: var(--color-text);\n font-weight: bold;\n margin: 1rem 0;\n}\n\n.Questions-content {\n flex-grow: 1;\n margin: 0 auto;\n max-width: 75.75rem;\n padding: 0 1.5rem;\n}\n\n.Questions-content a {\n color: var(--color-bright-text-link);\n}\n\n.Questions-content ul {\n list-style: none;\n padding-inline-start: 0;\n}\n\n.Questions-content ul > li {\n font-size: 0.875rem;\n line-height: 1.75rem;\n}\n"],
- "mappings": ";;;;;AAOA,eACE,aAGF,eACE,mCACA,cACA,aAdF,qBAgBE,WAGF,iCACE,0BAEF,oCACE,+CACE,2BAGJ,0CACE,UA5BF,iBAgCE,eAhCF,oBAqCA,iBACE,uBAEA,YAxCF,qBA0CE,qBACA,kBACA,WAGF,wBACE,2EACA,WACA,YACA,YACA,kBACA,aACA,UAGF,uDACE,oBAOF,mCACE,gDACA,6CACA,oBAGF,4BACE,uBACA,eAGF,oCACE,aAEF,0CACE,mCACE,6BACA,0BAGF,oCACE,qBAIJ,8CACE,aAGF,eA9FA,YAgGE,qBACA,WAGF,iCACE,aAGF,mBACE,mBACA,aACA,sBACA,kBACA,eACA,8BA9GF,cAgHE,qBACA,mBACA,cAEF,0CACE,mBACE,oBAIJ,wBACE,+BACA,gBACA,yBAGF,uBACE,aACA,YACA,eACA,eAGF,oBACE,mBACA,oBACA,cACA,oBACA,kBACA,mBAGF,uBACE,YACA,mBACA,kBACA,aACA,WAGF,kBAxJA,YA0JE,qBACA,WAGF,yBACE,wBACA,gBAGF,qBACE,kBApKF,iBAwKA,wBACE,eACA,oBAGF,WACE,4CACA,wBACA,aACA,oBACA,kBAGF,kBACE,wBACA,gBAvLF,cA2LA,mBACE,YA5LF,cA8LE,mBA9LF,iBAkMA,qBACE,oCAGF,sBACE,gBACA,uBAGF,yBACE,kBACA",
+ "sourcesContent": ["/*!\n * Copyright 2020 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/* Hide the search form in the header. */\n.go-SearchForm {\n display: none;\n}\n\n.Homepage-logo {\n border-radius: var(--border-radius);\n display: block;\n height: 10rem;\n margin: 3.125rem auto;\n width: auto;\n}\n\n[data-theme='dark'] .Homepage-logo {\n mix-blend-mode: difference;\n}\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme='light']) .Homepage-logo {\n mix-blend-mode: difference;\n }\n}\n@media only screen and (min-width: 52rem) {\n .Homepage {\n margin: 2rem auto;\n }\n\n .Homepage-logo {\n margin: 3.5rem auto;\n }\n}\n\n.Homepage-search {\n --border-radius: 0.5rem;\n\n height: 3rem;\n margin: 2.5rem auto 0;\n max-width: 45.0625rem;\n position: relative;\n width: 100%;\n}\n\n.Homepage-search::before {\n background: url('/static/shared/icon/search_gm_grey_24dp.svg') left no-repeat;\n content: '';\n height: 3rem;\n left: 0.75rem;\n position: absolute;\n width: 1.5rem;\n z-index: 3;\n}\n\n.Homepage-search .go-Select {\n padding-left: 2.5rem;\n}\n\n.Homepage-search .go-Input {\n padding-left: 2.5rem;\n}\n\n.Homepage-search--symbol .go-Input {\n border-bottom-right-radius: var(--border-radius);\n border-top-right-radius: var(--border-radius);\n padding-left: 2.5rem;\n}\n\n.Homepage-search .go-Button {\n justify-content: center;\n width: 7.375rem;\n}\n\n.Homepage-search--symbol .go-Button {\n display: none;\n}\n@media only screen and (min-width: 30rem) {\n .Homepage-search--symbol .go-Input {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n }\n\n .Homepage-search--symbol .go-Button {\n display: inline-flex;\n }\n}\n\ninput[type='search']::-webkit-search-decoration {\n display: none;\n}\n\n.Homepage-tips {\n margin: auto;\n max-width: 45.0625rem;\n width: 100%;\n}\n\n[data-local='true'] .Homepage-tips {\n display: none;\n}\n\n.Homepage-examples {\n align-items: center;\n display: flex;\n flex-direction: column;\n font-size: 0.875rem;\n gap: 0.5rem 1rem;\n justify-content: space-between;\n margin: 0 auto;\n max-width: 45.0625rem;\n white-space: nowrap;\n width: inherit;\n}\n@media only screen and (min-width: 52rem) {\n .Homepage-examples {\n flex-direction: row;\n }\n}\n\n.Homepage-examplesTitle {\n color: var(--color-text-subtle);\n font-weight: 500;\n text-transform: uppercase;\n}\n\n.Homepage-examplesList {\n display: flex;\n flex-grow: 1;\n flex-wrap: wrap;\n gap: 0.5rem 2rem;\n}\n\na.Homepage-helpLink {\n align-items: center;\n display: inline-flex;\n font-size: 1em;\n font-weight: initial;\n margin-left: 0.5rem;\n white-space: nowrap;\n}\n\n.Homepage-helpLink img {\n height: 1rem;\n margin-left: 0.25rem;\n position: relative;\n top: 0.1875rem;\n width: 1rem;\n}\n\n/* Local mode homepage styles */\n.Homepage-local {\n max-width: 60rem;\n margin: 0 auto;\n padding: 1.5rem;\n}\n\n.Homepage-search--local {\n margin: 1rem auto 2rem;\n}\n\n.Homepage-localSection {\n margin-bottom: 2rem;\n}\n\n.Homepage-localSection h2 {\n font-size: 1.125rem;\n margin: 0 0 0.75rem;\n padding-bottom: 0.5rem;\n border-bottom: var(--border);\n}\n\n.Homepage-localSection ul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.Homepage-localSection li {\n padding: 0.25rem 0;\n}\n\n.Homepage-mainModule ul {\n background: var(--color-background-accented);\n border-radius: var(--border-radius);\n padding: 1rem;\n}\n\n.Homepage-mainModule li {\n padding: 0.5rem 0;\n}\n\n.Homepage-mainModule a {\n font-size: 1.125rem;\n font-weight: 500;\n}\n\n.Homepage-mainModule small {\n color: var(--color-text-subtle);\n display: block;\n font-size: 0.8125rem;\n margin-top: 0.25rem;\n}\n\n.Homepage-mainModule {\n margin-bottom: 2.5rem;\n}\n\n.Homepage-dependencies {\n margin-bottom: 0.5rem;\n}\n\n.Homepage-transitive {\n margin: 0;\n}\n\n.Homepage-transitive summary {\n cursor: pointer;\n color: var(--color-text-subtle);\n font-size: 0.9375rem;\n padding-left: 0.25rem;\n}\n\n.Homepage-transitive[open] summary {\n margin-bottom: 0.25rem;\n}\n\n.Homepage-stdlib {\n text-align: center;\n margin: 1rem 0 0;\n padding-top: 0.75rem;\n border-top: var(--border);\n}\n\n.Questions {\n background: var(--color-background-accented);\n color: var(--color-text);\n display: flex;\n padding-bottom: 1rem;\n padding-top: 0.5rem;\n}\n\n.Questions-header {\n color: var(--color-text);\n font-weight: bold;\n margin: 1rem 0;\n}\n\n.Questions-content {\n flex-grow: 1;\n margin: 0 auto;\n max-width: 75.75rem;\n padding: 0 1.5rem;\n}\n\n.Questions-content a {\n color: var(--color-bright-text-link);\n}\n\n.Questions-content ul {\n list-style: none;\n padding-inline-start: 0;\n}\n\n.Questions-content ul > li {\n font-size: 0.875rem;\n line-height: 1.75rem;\n}\n"],
+ "mappings": ";;;;;AAOA,eACE,aAGF,eACE,mCACA,cACA,aAdF,qBAgBE,WAGF,iCACE,0BAEF,oCACE,+CACE,2BAGJ,0CACE,UA5BF,iBAgCE,eAhCF,oBAqCA,iBACE,uBAEA,YAxCF,qBA0CE,qBACA,kBACA,WAGF,wBACE,2EACA,WACA,YACA,YACA,kBACA,aACA,UAGF,uDACE,oBAOF,mCACE,gDACA,6CACA,oBAGF,4BACE,uBACA,eAGF,oCACE,aAEF,0CACE,mCACE,6BACA,0BAGF,oCACE,qBAIJ,8CACE,aAGF,eA9FA,YAgGE,qBACA,WAGF,iCACE,aAGF,mBACE,mBACA,aACA,sBACA,kBACA,eACA,8BA9GF,cAgHE,qBACA,mBACA,cAEF,0CACE,mBACE,oBAIJ,wBACE,+BACA,gBACA,yBAGF,uBACE,aACA,YACA,eACA,eAGF,oBACE,mBACA,oBACA,cACA,oBACA,kBACA,mBAGF,uBACE,YACA,mBACA,kBACA,aACA,WAIF,gBACE,gBA1JF,6BA+JA,wBA/JA,sBAmKA,uBACE,mBAGF,0BACE,mBAxKF,kBA0KE,qBACA,4BAGF,0BACE,gBA/KF,mBAoLA,0BApLA,iBAwLA,wBACE,4CACA,mCA1LF,aA8LA,wBA9LA,gBAkMA,uBACE,mBACA,gBAGF,2BACE,+BACA,cACA,mBACA,kBAGF,qBACE,qBAGF,uBACE,oBAGF,qBAtNA,SA0NA,6BACE,eACA,+BACA,mBACA,oBAGF,mCACE,qBAGF,iBACE,kBAtOF,gBAwOE,mBACA,yBAGF,WACE,4CACA,wBACA,aACA,oBACA,kBAGF,kBACE,wBACA,gBAtPF,cA0PA,mBACE,YA3PF,cA6PE,mBA7PF,iBAiQA,qBACE,oCAGF,sBACE,gBACA,uBAGF,yBACE,kBACA",
"names": []
}
diff --git a/static/frontend/homepage/homepage.tmpl b/static/frontend/homepage/homepage.tmpl
index 384c996..9d09063 100644
--- a/static/frontend/homepage/homepage.tmpl
+++ b/static/frontend/homepage/homepage.tmpl
@@ -10,66 +10,122 @@
{{define "main"}}
<main class="go-Container" id="main-content">
- <div class="go-Content go-Content--center">
- <img class="Homepage-logo" width="700" height="300"
- src="/static/shared/gopher/package-search-700x300.jpeg" alt="Cartoon gopher typing">
- <form class="go-InputGroup Homepage-search Homepage-search--symbol"
- action="/search" role="search" data-gtmc="homepage search form">
- <input
- class="go-Input js-searchFocus"
- data-test-id="homepage-search"
- id="AutoComplete"
- role="textbox"
- aria-label="{{.SearchPrompt}}"
- aria-describedby="SearchTipContent"
- type="search"
- name="q"
- placeholder="{{.SearchPrompt}}"
- autocapitalize="off"
- autocomplete="off"
- autocorrect="off"
- spellcheck="false"
- title="{{.SearchPrompt}}"
- autofocus="true">
- <button type="submit" class="go-Button">Search</button>
- </form>
- <section class="go-Carousel Homepage-tips js-carousel" aria-label="Search Tips Carousel" data-slide-index="{{.TipIndex}}">
- <ul>
- {{range $i, $v := .SearchTips}}
- <li class="go-Carousel-slide" {{if not (eq $.TipIndex $i)}}aria-hidden{{end}} {{if (eq $.TipIndex $i)}}id="SearchTipContent"{{end}}>
- <p>
- <strong>Tip:</strong> {{.Text}}
- <a href="/search?q={{$v.Example1}}">“{{$v.Example1}}”</a> or
- <a href="/search?q={{$v.Example2}}">“{{$v.Example2}}”</a>.
- <a href="/search-help" target="_blank" rel="noopener" class="Homepage-helpLink">
- Search help <span><img width="24" height="24" src="/static/shared/icon/launch_gm_grey_24dp.svg" alt=""></span>
- </a>
- </p>
- </li>
- {{end}}
- </ul>
- </section>
- {{if .LocalModules}}
- <section class="Homepage-modules" aria-label="Local Modules">
- <div class="Homepage-modules-header">Or browse local modules:</div>
+ {{if .LocalModules}}
+ <div class="go-Content Homepage-local">
+ <form class="go-InputGroup Homepage-search Homepage-search--symbol Homepage-search--local"
+ action="/search" role="search" data-gtmc="homepage search form">
+ <input
+ class="go-Input js-searchFocus"
+ data-test-id="homepage-search"
+ id="AutoComplete"
+ role="textbox"
+ aria-label="{{.SearchPrompt}}"
+ type="search"
+ name="q"
+ placeholder="{{.SearchPrompt}}"
+ autocapitalize="off"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ title="{{.SearchPrompt}}"
+ autofocus="true">
+ <button type="submit" class="go-Button">Search</button>
+ </form>
+
+ <section class="Homepage-localSection Homepage-mainModule" aria-label="Local Modules">
+ <h2>Local Modules</h2>
<ul>
- {{range .LocalModules}}<li><a href="/{{.ModulePath}}">{{.ModulePath}}</a> – {{.Dir}}</li>{{end}}
+ {{range .LocalModules}}
+ <li>
+ <a href="/{{.ModulePath}}">{{.ModulePath}}</a>
+ <small>{{.Dir}}</small>
+ </li>
+ {{end}}
</ul>
</section>
- {{end}}
- </div>
+
+ {{if .DirectDependencies}}
+ <section class="Homepage-localSection Homepage-dependencies" aria-label="Dependencies">
+ <h2>Dependencies</h2>
+ <ul>
+ {{range .DirectDependencies}}
+ <li><a href="/{{.ModulePath}}">{{.ModulePath}}</a></li>
+ {{end}}
+ </ul>
+ </section>
+ {{end}}
+
+ {{if .IndirectDependencies}}
+ <details class="Homepage-localSection Homepage-transitive">
+ <summary>Indirect Dependencies ({{len .IndirectDependencies}})</summary>
+ <ul>
+ {{range .IndirectDependencies}}
+ <li><a href="/{{.ModulePath}}">{{.ModulePath}}</a></li>
+ {{end}}
+ </ul>
+ </details>
+ {{end}}
+
+ <section class="Homepage-localSection Homepage-stdlib" aria-label="Standard Library">
+ <a href="/std">Browse Standard Library</a>
+ </section>
+ </div>
+ {{else}}
+ <div class="go-Content go-Content--center">
+ <img class="Homepage-logo" width="700" height="300"
+ src="/static/shared/gopher/package-search-700x300.jpeg" alt="Cartoon gopher typing">
+ <form class="go-InputGroup Homepage-search Homepage-search--symbol"
+ action="/search" role="search" data-gtmc="homepage search form">
+ <input
+ class="go-Input js-searchFocus"
+ data-test-id="homepage-search"
+ id="AutoComplete"
+ role="textbox"
+ aria-label="{{.SearchPrompt}}"
+ aria-describedby="SearchTipContent"
+ type="search"
+ name="q"
+ placeholder="{{.SearchPrompt}}"
+ autocapitalize="off"
+ autocomplete="off"
+ autocorrect="off"
+ spellcheck="false"
+ title="{{.SearchPrompt}}"
+ autofocus="true">
+ <button type="submit" class="go-Button">Search</button>
+ </form>
+ <section class="go-Carousel Homepage-tips js-carousel" aria-label="Search Tips Carousel" data-slide-index="{{.TipIndex}}">
+ <ul>
+ {{range $i, $v := .SearchTips}}
+ <li class="go-Carousel-slide" {{if not (eq $.TipIndex $i)}}aria-hidden{{end}} {{if (eq $.TipIndex $i)}}id="SearchTipContent"{{end}}>
+ <p>
+ <strong>Tip:</strong> {{.Text}}
+ <a href="/search?q={{$v.Example1}}">“{{$v.Example1}}”</a> or
+ <a href="/search?q={{$v.Example2}}">“{{$v.Example2}}”</a>.
+ <a href="/search-help" target="_blank" rel="noopener" class="Homepage-helpLink">
+ Search help <span><img width="24" height="24" src="/static/shared/icon/launch_gm_grey_24dp.svg" alt=""></span>
+ </a>
+ </p>
+ </li>
+ {{end}}
+ </ul>
+ </section>
+ </div>
+ {{end}}
</main>
{{end}}
{{define "pre-footer"}}
- <div class="Questions">
- <div class="Questions-content">
- <div class="Questions-header" role="heading" aria-level="2">Frequently asked questions:</div>
- <ul>
- <li><a href="https://go.dev/about#adding-a-package">How can I add a package?</a></li>
- <li><a href="https://go.dev/about#removing-a-package">How can I remove a package?</a></li>
- <li><a href="https://go.dev/about#creating-a-badge">How can I add a go badge in my README file?</a></li>
- </ul>
+ {{if not .LocalModules}}
+ <div class="Questions">
+ <div class="Questions-content">
+ <div class="Questions-header" role="heading" aria-level="2">Frequently asked questions:</div>
+ <ul>
+ <li><a href="https://go.dev/about#adding-a-package">How can I add a package?</a></li>
+ <li><a href="https://go.dev/about#removing-a-package">How can I remove a package?</a></li>
+ <li><a href="https://go.dev/about#creating-a-badge">How can I add a go badge in my README file?</a></li>
+ </ul>
+ </div>
</div>
- </div>
+ {{end}}
{{end}}