├── .gitignore ├── .goreleaser.yml ├── CONTRIBUTING.md ├── Makefile ├── Readme.md ├── cmd └── static-docs │ └── main.go ├── docs ├── docs.go └── themes │ └── apex │ ├── apex.go │ ├── bindata.go │ ├── doc.go │ └── files │ ├── css │ └── index.css │ ├── js │ └── index.js │ └── views │ └── index.html ├── frontmatter.go ├── frontmatter_test.go ├── html.go ├── html_highlight.go ├── html_highlight_test.go ├── html_test.go ├── inject ├── inject.go └── inject_test.go ├── markdown.go ├── markdown_test.go └── testdata ├── highlight_input.html └── highlight_output.html /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .envrc 3 | dist 4 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - 3 | main: cmd/static-docs/main.go 4 | binary: static-docs 5 | goos: 6 | - darwin 7 | - linux 8 | - windows 9 | - freebsd 10 | - netbsd 11 | - openbsd 12 | goarch: 13 | - amd64 14 | - 386 15 | changelog: 16 | sort: asc 17 | filters: 18 | exclude: 19 | - '^docs:' 20 | - '^refactor' 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | ## Editing Themes 3 | 4 | For now the best way to work on a theme is to run `static-docs` to produce the site: 5 | 6 | ``` 7 | $ make 8 | ``` 9 | 10 | Which produces the following files: 11 | 12 | ``` 13 | build 14 | ├── index.html 15 | └── theme 16 | └── apex 17 | ├── css 18 | │ └── index.css 19 | ├── js 20 | │ ├── index.js 21 | │ └── smoothscroll.js 22 | └── views 23 | └── index.html 24 | ``` 25 | 26 | Open the site and edit anything you like. 27 | 28 | ``` 29 | $ open build/index.html 30 | ``` 31 | 32 | Copy and paste any changes back into `./docs/themes/apex`. 33 | 34 | This process will be improved at some point. 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include github.com/tj/make/golang 2 | 3 | .DEFAULT_GOAL := generate 4 | 5 | # Generate themes. 6 | generate: 7 | @go generate ./... 8 | .PHONY: generate 9 | 10 | # Clean build artifacts. 11 | clean: 12 | @echo "==> Clean" 13 | @rm -fr build 14 | .PHONY: clean 15 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | The goal of this project is to build a common toolkit for building static sites for a variety of domains. Each program is tailored to one domain, such as a blog, documentation site, photo gallery, and so on. For example. Focusing the UX on each domain as necessary makes for a smoother experience, nicer content structuring, while maintaining an overall common feel and usability through the shared library. The shared library or "static stdlib" will also make it easy to write custom static generators to fit your needs. 4 | 5 | I think this technique makes much more sense than fighting tools which are designed for blogs, as we all know how to write code, it can sometime save hours to just write a few lines instead of fighting a complex system that tries to do everything. I don't have much time for OSS right now, so it only has what I need, but hopefully long-term it'll turn into something real. 6 | 7 | Deploy in a single command to the [Netlify](https://www.netlify.com/) CDN, or to AWS using [Apex Up](https://github.com/apex/up). 8 | 9 | ## Install 10 | 11 | ```bash 12 | $ go get github.com/apex/static/cmd/static-docs 13 | ``` 14 | 15 | ## Usage 16 | 17 | The `static-docs` program generates a documentation website from a directory of markdown files. For example the [Up](https://up.docs.apex.sh/) documentation is generated with: 18 | 19 | ``` 20 | $ static-docs --in docs --out . --title Up --subtitle "Deploy serverless apps in seconds" 21 | ``` 22 | 23 | --- 24 | 25 | [![GoDoc](https://godoc.org/github.com/apex/static?status.svg)](https://godoc.org/github.com/apex/static) 26 | ![](https://img.shields.io/badge/license-MIT-blue.svg) 27 | ![](https://img.shields.io/badge/status-stable-green.svg) 28 | 29 | 30 | -------------------------------------------------------------------------------- /cmd/static-docs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/apex/log" 8 | "github.com/apex/log/handlers/cli" 9 | "github.com/tj/kingpin" 10 | 11 | "github.com/apex/static/docs" 12 | ) 13 | 14 | func init() { 15 | log.SetHandler(cli.Default) 16 | } 17 | 18 | func main() { 19 | app := kingpin.New("static-docs", "Generate static documentation sites") 20 | app.Example("static-docs --title Up --in ./docs", "Generate site in ./build from ./docs/*.md") 21 | app.Example("static-docs --title Up --in ../up/docs -out .", "Generate a site in ./ from ../up/docs/*.md") 22 | title := app.Flag("title", "Site title.").String() 23 | subtitle := app.Flag("subtitle", "Site subtitle or slogan.").String() 24 | theme := app.Flag("theme", "Theme name.").Default("apex").String() 25 | src := app.Flag("in", "Source directory for markdown files.").Default(".").String() 26 | dst := app.Flag("out", "Output directory for the static site.").Default("build").String() 27 | segment := app.Flag("segment", "Segment write key.").String() 28 | google := app.Flag("google", "Google Analytics tracking id.").String() 29 | kingpin.MustParse(app.Parse(os.Args[1:])) 30 | 31 | println() 32 | defer println() 33 | 34 | start := time.Now() 35 | 36 | c := &docs.Config{ 37 | Src: *src, 38 | Dst: *dst, 39 | Title: *title, 40 | Subtitle: *subtitle, 41 | Theme: *theme, 42 | Segment: *segment, 43 | Google: *google, 44 | } 45 | 46 | if err := docs.Compile(c); err != nil { 47 | log.Fatalf("error: %s", err) 48 | } 49 | 50 | log.Infof("compiled in %s\n", time.Since(start).Round(time.Millisecond)) 51 | } 52 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | package docs 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/apex/log" 13 | "github.com/pkg/errors" 14 | "github.com/segmentio/go-snakecase" 15 | 16 | "github.com/apex/static" 17 | "github.com/apex/static/docs/themes/apex" 18 | "github.com/apex/static/inject" 19 | ) 20 | 21 | // Config options. 22 | type Config struct { 23 | // Src dir of markdown documentation files. 24 | Src string 25 | 26 | // Dst dir of the static site. 27 | Dst string 28 | 29 | // Title of the site. 30 | Title string 31 | 32 | // Subtitle of the site. 33 | Subtitle string 34 | 35 | // Theme name. 36 | Theme string 37 | 38 | // Segment write key. 39 | Segment string 40 | 41 | // Google Analytics tracking id. 42 | Google string 43 | } 44 | 45 | // Page model. 46 | type Page struct { 47 | // Title of the page. 48 | Title string `yml:"title"` 49 | 50 | // Slug of the page. 51 | Slug string `yml:"slug"` 52 | 53 | // Skip the page. 54 | Skip bool `yml:"skip"` 55 | 56 | // Content of the page. 57 | Content template.HTML 58 | } 59 | 60 | // Compile docs site. 61 | func Compile(c *Config) error { 62 | log.Infof("compiling %s to %s", c.Src, c.Dst) 63 | 64 | if err := os.MkdirAll(c.Dst, 0755); err != nil { 65 | return errors.Wrap(err, "mkdir") 66 | } 67 | 68 | if err := initTheme(c); err != nil { 69 | return errors.Wrap(err, "initializing theme") 70 | } 71 | 72 | files, err := ioutil.ReadDir(c.Src) 73 | if err != nil { 74 | return errors.Wrap(err, "reading dir") 75 | } 76 | 77 | var pages []*Page 78 | 79 | for _, f := range files { 80 | path := filepath.Join(c.Src, f.Name()) 81 | 82 | log.Infof("compiling %q", path) 83 | p, err := compile(c, path) 84 | if err != nil { 85 | return errors.Wrapf(err, "compiling %q", path) 86 | } 87 | 88 | if p == nil { 89 | log.Infof("skipping %q", path) 90 | continue 91 | } 92 | 93 | pages = append(pages, p) 94 | } 95 | 96 | var buf bytes.Buffer 97 | 98 | if err := render(&buf, c, pages); err != nil { 99 | return errors.Wrap(err, "rendering") 100 | } 101 | 102 | html := buf.String() 103 | 104 | if c.Segment != "" { 105 | html = inject.Head(html, inject.Segment(c.Segment)) 106 | } 107 | 108 | if c.Google != "" { 109 | html = inject.Head(html, inject.GoogleAnalytics(c.Google)) 110 | } 111 | 112 | out := filepath.Join(c.Dst, "index.html") 113 | 114 | body := ioutil.NopCloser(strings.NewReader(html)) 115 | body = static.HeadingAnchors(body) 116 | body = static.SyntaxHighlight(body) 117 | 118 | f, err := os.OpenFile(out, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) 119 | if err != nil { 120 | return errors.Wrap(err, "opening") 121 | } 122 | 123 | if _, err := io.Copy(f, body); err != nil { 124 | return errors.Wrap(err, "copying") 125 | } 126 | 127 | if err := f.Close(); err != nil { 128 | return errors.Wrap(err, "closing") 129 | } 130 | 131 | return nil 132 | } 133 | 134 | // render to writer w. 135 | func render(w io.Writer, c *Config, pages []*Page) error { 136 | path := filepath.Join(c.Dst, "theme", c.Theme, "views", "*.html") 137 | 138 | views, err := template.ParseGlob(path) 139 | if err != nil { 140 | return errors.Wrap(err, "parsing templates") 141 | } 142 | 143 | err = views.ExecuteTemplate(w, "index.html", struct { 144 | *Config 145 | Pages []*Page 146 | }{ 147 | Config: c, 148 | Pages: pages, 149 | }) 150 | 151 | if err != nil { 152 | return errors.Wrap(err, "rendering") 153 | } 154 | 155 | return nil 156 | } 157 | 158 | // compile file. 159 | func compile(c *Config, path string) (*Page, error) { 160 | // open 161 | f, err := os.Open(path) 162 | if err != nil { 163 | return nil, errors.Wrap(err, "open") 164 | } 165 | defer f.Close() 166 | 167 | // meta-data 168 | var page Page 169 | rc := static.Notes(static.Markdown(static.Frontmatter(f, &page))) 170 | 171 | // contents 172 | b, err := ioutil.ReadAll(rc) 173 | if err != nil { 174 | rc.Close() 175 | return nil, errors.Wrap(err, "reading") 176 | } 177 | 178 | if err := rc.Close(); err != nil { 179 | return nil, errors.Wrap(err, "closing") 180 | } 181 | 182 | // populate 183 | if page.Slug == "" { 184 | page.Slug = snakecase.Snakecase(page.Title) 185 | } 186 | page.Content = template.HTML(b) 187 | 188 | // skip 189 | if page.Skip { 190 | return nil, nil 191 | } 192 | 193 | return &page, nil 194 | } 195 | 196 | // initTheme populates the theme directory unless present. 197 | func initTheme(c *Config) error { 198 | dir := filepath.Join(c.Dst, "theme", c.Theme) 199 | _, err := os.Stat(dir) 200 | 201 | if os.IsNotExist(err) { 202 | return apex.RestoreAssets(dir, "") 203 | } 204 | 205 | return nil 206 | } 207 | 208 | // stripExt returns the path sans-extname. 209 | func stripExt(s string) string { 210 | return strings.Replace(s, filepath.Ext(s), "", 1) 211 | } 212 | -------------------------------------------------------------------------------- /docs/themes/apex/apex.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -modtime 0 -pkg apex -prefix files files/... 2 | 3 | package apex 4 | -------------------------------------------------------------------------------- /docs/themes/apex/bindata.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-bindata. 2 | // sources: 3 | // files/css/index.css 4 | // files/js/index.js 5 | // files/views/index.html 6 | // DO NOT EDIT! 7 | 8 | package apex 9 | 10 | import ( 11 | "bytes" 12 | "compress/gzip" 13 | "fmt" 14 | "io" 15 | "io/ioutil" 16 | "os" 17 | "path/filepath" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | func bindataRead(data []byte, name string) ([]byte, error) { 23 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 24 | if err != nil { 25 | return nil, fmt.Errorf("Read %q: %v", name, err) 26 | } 27 | 28 | var buf bytes.Buffer 29 | _, err = io.Copy(&buf, gz) 30 | clErr := gz.Close() 31 | 32 | if err != nil { 33 | return nil, fmt.Errorf("Read %q: %v", name, err) 34 | } 35 | if clErr != nil { 36 | return nil, err 37 | } 38 | 39 | return buf.Bytes(), nil 40 | } 41 | 42 | type asset struct { 43 | bytes []byte 44 | info os.FileInfo 45 | } 46 | 47 | type bindataFileInfo struct { 48 | name string 49 | size int64 50 | mode os.FileMode 51 | modTime time.Time 52 | } 53 | 54 | func (fi bindataFileInfo) Name() string { 55 | return fi.name 56 | } 57 | func (fi bindataFileInfo) Size() int64 { 58 | return fi.size 59 | } 60 | func (fi bindataFileInfo) Mode() os.FileMode { 61 | return fi.mode 62 | } 63 | func (fi bindataFileInfo) ModTime() time.Time { 64 | return fi.modTime 65 | } 66 | func (fi bindataFileInfo) IsDir() bool { 67 | return false 68 | } 69 | func (fi bindataFileInfo) Sys() interface{} { 70 | return nil 71 | } 72 | 73 | var _cssIndexCss = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x58\x5f\x6f\xe3\xb8\x11\x7f\xf7\xa7\x18\x38\x38\x60\xd3\x5a\x82\xe4\xc4\x89\x57\x0b\x2c\x7a\xdd\xdb\xe0\xfa\xb0\xed\xa1\xe9\xa1\x7d\xa5\xa4\x91\xc4\x86\x22\x05\x92\x4a\x9c\x2d\xf2\xdd\x0b\xfe\x91\x4c\xda\x72\xee\x0e\x79\x88\x44\x0e\x67\x86\xbf\xf9\xcd\x1f\xb9\x90\x42\x68\xf8\xdf\x0a\x20\x49\x90\x28\x2c\xa0\x1a\x4b\x5a\x25\x25\x7e\xa7\x28\x3f\xa4\xfb\xed\x06\xb2\x0d\xa4\xf9\x76\x03\xf9\xf5\x27\x2b\xf7\x42\x6b\xdd\x15\xb0\xcf\xb2\xe1\xe0\x56\x3a\x24\x35\xca\xa4\x43\xda\x76\xba\x80\x5b\xb7\x63\xb7\xb4\x24\xd5\x13\xe5\x6d\x01\x59\x9a\xed\x24\xf6\x9f\xa2\xe5\xa4\xc7\x9a\x8e\xbd\xd9\x5d\xd8\x64\x44\xb6\x68\xf6\xf6\x76\xcf\x6e\xd6\x44\x3e\x15\x70\x95\x65\x99\x13\x2e\xd9\x88\x05\x5c\xdd\xdc\xfc\xf8\xf1\xe1\xc1\x2d\x31\xe3\x46\xd2\x4a\xf2\x5a\xc0\x55\x43\xcc\x9f\x3f\x5c\xb6\x66\xa5\x69\x9c\x5c\x63\xde\xf6\x77\xfb\xaf\x1f\xef\xa6\x85\x49\xfd\x36\xdf\xee\xb6\x1f\xdd\x6a\x2b\xc9\x60\x4c\xec\x9a\x9b\xba\xba\xf5\x9a\x14\x32\xac\x34\x15\xdc\xea\x7c\x26\xf2\x83\x73\xc5\x63\x74\xdc\x36\x46\x5e\x3a\xaa\xf1\xd3\xea\x6d\xb5\xfa\x93\xc5\xba\x14\x87\x44\xd1\xef\x16\x96\x52\x48\x83\x5d\x29\x0e\x56\xa0\xd3\x3d\xdb\x40\x29\xea\x57\x2b\xd9\x08\xae\x93\x86\xf4\x94\xbd\x16\x90\x90\x61\x60\x98\xa8\x57\xa5\xb1\xdf\xc0\x5f\x19\xe5\x4f\xdf\x48\xf5\x68\xdf\x1f\x04\xd7\x9b\x15\x00\xc0\xfa\x11\x5b\x81\xf0\xeb\xdf\xd6\x1b\x58\xff\x53\x94\x42\x0b\xf3\xf4\x8f\xc3\x6b\x8b\x7c\xed\x65\x7e\x2d\x47\xae\x47\xb3\xfe\x85\x70\x4d\x24\x32\x66\x5e\x1e\xa8\x24\xf0\x48\xb8\x9a\xe4\x7e\x92\x82\xd6\x7e\x05\xd6\x3f\x23\x7b\x46\x4d\x2b\x02\x7f\xc7\x11\xd7\x1b\x50\x84\xab\x44\xa1\xa4\x16\x52\xeb\xad\xa2\xdf\xb1\x80\x7c\xe7\xc8\x61\x97\x5e\x3c\x31\x6e\x5c\xd0\x4a\x52\x3d\xb5\x52\x8c\xbc\x9e\x91\x6b\x2d\x6e\x95\x60\x42\x4e\x6b\x3e\x18\x76\x83\x51\x8e\x33\xbd\x72\xb3\xd2\x13\xd9\x52\x5e\x80\x55\x38\x90\xba\x76\x1c\xb3\x18\x16\xc5\x0c\xbf\x83\xfb\xcc\x5e\x18\xbd\x73\xcb\x61\xf0\xae\x5d\x54\xf2\x0d\x74\xdb\x0d\x74\x37\x1b\xe8\x6e\xad\x52\xe7\x40\xa2\xc5\x50\xc0\xbd\xbf\xac\x5f\x2b\x85\xd6\xa2\xf7\xbe\x85\x98\xa4\x5b\xcf\xf1\x08\x95\x3b\x87\x4a\x7c\xc7\x39\x1d\x2e\x60\x62\x9d\x3a\x73\xe4\xdc\xa2\x53\xe3\xa4\xff\x0c\xc3\x91\x54\xb1\xc0\x99\xf9\x3b\x7b\x88\x58\x79\xef\x02\xe5\x1d\x4a\xaa\x8d\xb0\xc6\x83\x4e\x6a\xac\x84\x24\x06\xa7\x02\xb8\xe0\x8e\xe0\x43\xe0\x53\x01\xdb\xdd\x70\x80\xa5\xdb\xdd\xef\x96\xee\x66\xdf\x5d\xfa\xba\x2b\x32\x0a\x4a\x4b\xc1\xdb\xcd\x6a\xf0\x4f\xa1\x47\xe7\x44\x89\x80\xdd\x65\xd9\xa4\x85\x18\x05\xe4\x0f\x9c\xbd\xcd\x42\x66\xcd\x21\xbd\x71\x81\x9e\x93\xd6\xad\xe6\xc3\x01\x6a\xa1\x35\xd6\x70\x55\xd7\xf5\x6c\xb4\xe8\xc4\x33\x4a\x6b\xda\x3d\x9e\x3b\x30\xd7\x8c\x13\x95\x01\xa0\xe1\xd9\x48\x2a\x89\x34\xd9\x17\x07\xdb\xc8\xa2\x28\xec\x32\x13\x05\xf7\xef\xc6\x17\xed\x93\x94\x19\x19\x9c\x1c\xca\xdd\xa1\xfc\x78\xc8\x89\x31\x1a\xeb\x9e\x02\xfc\x7e\x2c\xcf\xe9\xed\x69\xe9\x34\xbe\x13\xda\xe9\x5a\x17\x62\x3b\x48\xbc\x90\xe3\xc7\x2e\x70\x9e\xe1\x73\xcc\x67\x18\x26\x5c\x3c\xc0\x92\xd4\x74\x54\x05\x6c\xdd\xaa\xc1\xbf\x61\xe2\x25\x39\x14\x40\x46\x2d\x26\x77\x0a\x58\x3f\x8a\x51\x56\x08\x5f\x44\x8d\xf0\x8b\x34\x95\xf6\x1b\x72\x26\x36\xd0\x0b\x2e\xd4\x40\x2a\x3c\xc9\xc9\x74\xbf\x98\x70\x3b\xdf\xfd\x5e\xb0\x7c\xa2\x3a\x71\x07\x7a\x21\x74\x67\xfd\x23\x5c\x53\xc2\x28\x51\x38\xd3\xeb\x33\x54\xa2\x46\xc3\x2e\xf7\x14\x10\xc4\x51\x52\x09\x46\x6b\xb8\xfa\xe9\xeb\xd7\xed\xd7\xbb\x13\x2f\xb2\xf4\x7e\xca\xfc\x23\x06\xc3\xc1\xc6\x7b\x01\x07\xcf\x7b\xdb\xc8\x12\x7b\x2b\x43\xd1\x17\x49\x86\x4b\xe5\x2c\xea\x5c\x41\xe9\xf8\xbd\x17\xac\x51\x13\xca\x14\x7c\x06\x35\xf6\x3d\x91\xae\x1f\x56\xa3\x54\x26\x8c\x83\xa0\x5c\xa3\xb4\xb1\x19\xb5\x81\x72\x4a\x19\x80\x51\xa1\xf4\x35\x3c\xc8\xa3\xa3\xbe\x21\xcc\x24\x86\x8d\x76\x37\x77\x68\x39\x7e\xd8\x96\x1f\x12\xc4\xcb\xb9\xae\xf6\xb6\x5a\xa5\xff\x96\x64\x18\x7c\x52\xd6\x54\x0d\xcc\x0c\x1b\x0d\x43\x0b\xd3\x7f\x47\xa5\x69\xf3\x9a\x54\x82\x6b\x34\x2c\xa9\xd0\x79\x6b\x4e\x7e\x11\x5c\x13\xca\xfd\x59\x3f\x4c\x39\xb3\xf6\xc5\x9a\x3d\x53\x69\xfe\x27\x35\x95\xae\x31\x15\x86\xce\x63\xcf\x8f\x1a\xd1\x44\xe0\xb2\x4f\x46\xec\x67\x3b\xa5\xfd\x41\x8f\x01\x08\xa3\x2d\x4f\xa8\xc6\x5e\x85\xcb\x13\x73\x9d\xe7\xd1\x04\xe8\x8a\x50\xfa\x48\x6b\x2c\x89\x33\x68\xec\x14\x90\x43\xee\xd3\x27\x70\x3b\x84\xe1\x7e\xf7\x43\xbc\x47\xfb\xd6\x97\x9b\xc3\x34\x77\xe6\x59\xe6\x85\xbe\x21\x1f\xed\xee\x20\x14\x75\xb0\x4c\xec\x52\x9a\x56\x4f\xaf\x9f\xa2\xbd\xe3\x9a\xed\x95\x3b\xcf\xf4\x8b\xfd\xe0\xf7\x32\xd5\xf9\xf1\x19\x52\x83\x91\xf3\xe7\xa4\x6b\x1c\x69\x13\x89\x7e\xf6\x1d\xe9\xe8\xa2\x44\x46\x34\x7d\xbe\x44\xe3\xe5\x1e\xa5\x25\xe1\x93\x02\x7b\x19\xd8\x66\x59\xaf\x16\xee\x76\xbd\xec\x44\x4a\x2a\x63\xf4\xbd\xee\xb8\x74\xac\x28\xb1\x11\x72\x3a\xe6\x79\xb3\x5e\xc7\xa0\x93\x52\x09\x36\x6a\xeb\x7c\x14\xc0\x23\x83\xf2\xa9\xe2\x38\xb0\x12\x3f\x4c\xb9\x8c\x3b\x19\x18\x93\x8b\xd1\x7a\xa6\x8a\x96\x94\x51\xfd\x5a\x40\x47\xeb\x1a\xf9\x8c\x4d\x23\x64\x5f\x80\xaa\x08\xc3\xff\x7c\xc8\xae\xa3\xf5\x44\x48\x6a\x1b\x99\x31\x17\xd0\x3b\x04\x95\x30\x06\xdb\x5d\xd6\x2b\x6f\xd6\x7c\x28\x5d\xc2\xe4\x42\x9b\xff\x0d\x24\xed\xa9\x10\xcf\xf0\x36\xf6\x99\xe1\xf2\x75\x72\xaf\xf2\x17\xd2\xe2\xd9\x38\x98\x67\x71\xb7\x4f\x02\xe2\x4f\x87\x8a\x86\x4a\xa5\x93\xaa\xa3\xac\x5e\x9e\x27\xa3\xc3\xae\xeb\xa6\xff\xa2\x9a\xe1\xf2\x20\x10\xf5\xb5\x6d\xba\xf5\x8d\xde\x1d\x49\x1d\xc2\xa1\x21\x17\xe7\xa9\xab\xfa\x45\xe9\x8e\x4f\xab\x76\xde\xb4\x85\x28\x2c\x41\x41\x51\xd8\x65\xf3\xa5\x9c\x1d\xa7\x27\xb4\x33\xf1\x6b\x1f\x4b\x9a\x06\x33\x10\x1e\x97\xc5\x92\x89\xea\x29\x14\x4a\xd5\x58\x1a\x27\x2e\x8f\x28\xc1\xa8\x13\xb5\xd9\xbd\xef\xb2\xf6\x0a\x41\xfc\x46\x53\xaa\x2b\xa2\x30\x2a\xf7\x73\xbb\x9a\xec\xce\x46\x19\x6a\x6d\x0a\xc2\x40\x2a\x5b\x85\x9c\xf5\xe9\x53\xf9\xfa\x37\x4d\x44\x95\xa3\x14\xac\x3e\xfd\x54\xb8\x8b\x01\x54\xbd\xa1\xfd\xd1\x7e\x28\x7a\xeb\x58\x75\x6a\x6f\x2a\x52\xa7\xae\x72\x21\x7b\xc2\xce\xb9\x31\x7d\x9a\xa4\x3f\xf2\xaa\x13\xf2\x9d\x5a\x18\x51\x25\x99\xec\x0b\x63\xc0\x64\x48\x96\xe6\xbb\x08\x46\xca\xad\x25\x1f\xc6\x63\xe5\xf1\x07\xe7\xca\x73\x3b\xdd\xd9\x7b\xa0\x9e\xdb\x13\x2f\xc2\xfa\xe5\x49\xe9\xeb\x53\x98\x0e\xc1\xf1\x81\xe8\xce\xeb\xb0\x23\x4a\x82\xcf\xc8\xb5\xb2\x55\x24\x90\x0d\x0a\xc5\x7c\x8d\xdc\x29\x7b\x10\x62\x4a\x91\xd9\xd1\x23\xbd\xbf\xa1\x52\x71\xae\x87\x9f\x59\xf3\x2c\xb7\xf5\x5e\x86\x73\xf1\x15\xde\x37\x3b\xf7\xe3\xc7\xc2\x98\x78\x7f\x5b\x65\x4d\x75\x79\xf6\xf3\xac\xbf\xca\xab\x7b\xac\xef\x62\x67\xe2\xaf\x3e\x07\xca\x5f\x7a\xac\x29\x01\x55\x49\x44\x0e\x84\xd7\xf0\x21\x48\xd8\xbd\xa9\x42\xd7\xee\x92\xf1\x4f\x1e\x0b\x3f\x28\xdd\x4c\x75\x2c\xfa\xf9\xe9\x07\xb7\xb2\x40\xcc\xb7\xd5\x0a\x60\x71\x2c\x5a\x18\x7d\x2e\xcf\x56\x93\xa2\x70\x90\x39\x2b\x27\xd3\x87\x43\x64\xd3\x8b\x9e\x34\xbc\xb7\xd5\xdb\xea\xff\x01\x00\x00\xff\xff\x93\x84\x90\xe3\x71\x13\x00\x00") 74 | 75 | func cssIndexCssBytes() ([]byte, error) { 76 | return bindataRead( 77 | _cssIndexCss, 78 | "css/index.css", 79 | ) 80 | } 81 | 82 | func cssIndexCss() (*asset, error) { 83 | bytes, err := cssIndexCssBytes() 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | info := bindataFileInfo{name: "css/index.css", size: 4977, mode: os.FileMode(420), modTime: time.Unix(1522254193, 0)} 89 | a := &asset{bytes: bytes, info: info} 90 | return a, nil 91 | } 92 | 93 | var _jsIndexJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x91\x5f\x6b\xdb\x30\x14\xc5\xdf\xf5\x29\xce\x9b\xe5\x74\xb8\x7d\xef\x52\x68\x4b\x1f\x06\x1b\x8c\xe5\xb1\x14\xe2\xca\xd7\xf6\x25\xf2\x55\x26\x5d\x27\x84\xd1\xef\x3e\x2c\xbb\xeb\x56\x32\xfa\x20\x30\xf8\x77\x74\xfe\xe8\x72\xb5\x32\x58\x61\x43\x3a\xee\x2b\x83\xd5\xa5\x31\x2e\x48\x52\xb0\xd2\x90\xb0\x46\x13\xdc\x38\x90\x68\xf5\x73\xa4\x78\xda\x90\x27\xa7\x21\xde\x7a\x6f\x8b\xea\x7b\xdd\x51\x51\x2e\x02\xcf\xb2\xfb\x48\xf0\x8d\x64\x44\x5d\x94\xc6\x2c\xbe\xf7\x3d\xb9\x1d\xb8\xc5\x96\xfc\x16\x9c\x10\x46\x9d\x4f\x8b\x03\xd3\x71\x89\xd4\x8e\xe2\x94\x83\x80\xd3\x1d\xf9\x70\xdc\xb8\x18\xbc\xb7\xe4\x4b\xfc\x32\x40\x24\x1d\xa3\x80\x7c\xd5\x91\xde\x85\x51\x1a\x96\xee\xde\x33\x89\xfe\x20\xa7\xb6\xac\x9e\x83\x6a\x18\x70\x83\x2b\xf3\xf2\xc7\xfc\xd6\x29\x1f\x6a\xa5\xdc\x15\x5b\xde\xbe\x77\xab\x17\xe0\x8b\xd2\x60\x79\xf6\xca\x35\xab\x36\xc4\x87\xda\xf5\x96\xb0\xbe\x01\x55\xce\xd7\x29\x7d\xe5\xa4\x55\xa4\x21\x1c\xc8\x16\x59\x4a\x45\x59\xbe\x4a\x1e\xf9\xe9\x2f\xac\x6e\x9a\x37\xe6\x5c\x24\xed\x09\x2e\xc4\x48\x4e\x31\x4c\xab\xe5\x8c\x6d\x88\xd3\x9f\x89\x74\x41\x94\x44\x13\x58\x32\x3c\xad\xb5\x0f\x51\xff\xd7\xc1\x2e\xf1\x49\xc1\x58\xe3\xca\x18\xe4\xeb\xec\x35\x18\x9f\xe7\xe7\xae\x3c\x49\xa7\xfd\x35\xf8\xe2\x62\xc6\x31\xbd\x8d\xfd\x77\xf5\x8c\x3e\xf2\x53\xf9\x8a\x00\xcf\x91\xea\x5d\xfe\x7e\x31\xd3\x31\x78\xbf\xdd\xb9\x8e\x29\xdf\x87\xb4\x3f\x41\x7b\x96\xee\xb4\x64\x3f\xb2\x34\xe1\x38\x6d\xf4\x70\x20\xd1\x69\x30\x12\x8a\xb6\x98\x05\xc5\x27\xe4\xd9\xdf\x9a\x95\xe6\x77\x00\x00\x00\xff\xff\x00\x4d\x68\x65\xc7\x02\x00\x00") 94 | 95 | func jsIndexJsBytes() ([]byte, error) { 96 | return bindataRead( 97 | _jsIndexJs, 98 | "js/index.js", 99 | ) 100 | } 101 | 102 | func jsIndexJs() (*asset, error) { 103 | bytes, err := jsIndexJsBytes() 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | info := bindataFileInfo{name: "js/index.js", size: 711, mode: os.FileMode(420), modTime: time.Unix(1507930305, 0)} 109 | a := &asset{bytes: bytes, info: info} 110 | return a, nil 111 | } 112 | 113 | var _viewsIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x54\xc1\x8e\xdb\x20\x14\xbc\xf7\x2b\x5e\xe9\xb5\x8e\x95\x5b\x0f\xb0\x97\xb4\x55\x2f\x55\x57\xda\x95\xaa\x1e\x09\xbc\x0d\x6c\x31\x76\xcd\xb3\x93\x95\xeb\x7f\xaf\x6c\xc7\x15\xe0\x68\xf7\x14\xd0\xcc\x3c\xcf\x0c\x10\xfe\xfe\xf3\x8f\xc3\xe3\xaf\xfb\x2f\x60\xa8\x72\x77\xef\xf8\xf2\x03\xc0\x0d\x4a\x3d\x2d\x00\x78\x85\x24\x41\x19\xd9\x06\x24\xc1\x3a\x7a\x2a\x3e\xb1\x18\x32\x44\x4d\x81\x7f\x3a\xdb\x0b\x76\x29\x3a\x59\xa8\xba\x6a\x24\xd9\xa3\x43\x06\xaa\xf6\x84\x9e\x04\xb3\x28\x50\x9f\x30\x51\x7a\x59\xa1\x60\xbd\xc5\x73\x53\xb7\x14\x91\xcf\x56\x93\x11\x1a\x7b\xab\xb0\x98\x37\x1f\xc1\x7a\x4b\x56\xba\x22\x28\xe9\x50\xec\xd7\x41\x64\xc9\xe1\xdd\x30\xec\x1e\xa7\xc5\x38\xc2\x5f\x18\x86\xdd\x43\x77\xa4\x65\xcf\xcb\x85\xb1\xb0\x9d\xf5\xbf\xa1\x45\x27\x58\xa0\x17\x87\xc1\x20\x12\x03\xd3\xe2\x93\x60\x64\xb0\xc2\x52\x36\x78\x29\x55\x08\xa5\xf5\x1a\x2f\x3b\x15\xc2\xfc\x25\x5e\xae\x8d\xf0\x63\xad\x5f\xae\xe3\xb4\xed\x41\x39\x19\x82\x60\x3f\x5b\xd9\x34\xd8\x5e\x6d\xa5\xd8\xa1\xf6\x24\xad\x8f\xd0\x14\xff\x86\x52\x27\x60\x0a\xcf\xc9\x40\xa1\xa7\x8c\x04\xc0\x43\x23\xfd\xca\x23\xbc\x10\x8b\xaa\xe0\xe5\x84\xbe\x22\x08\xdd\xf1\xbf\x26\x6e\x2c\x97\xf1\x52\xdb\x3e\xb2\xbe\x6c\x6f\x46\x39\x2c\x47\x58\x9c\xb3\x3a\x72\xde\x83\xd5\x78\x94\x9b\x38\x11\xe3\x3b\xfa\x2e\x83\x01\x86\xa1\x95\xfe\x84\xb0\xbb\x97\x27\x0c\xe3\x98\xc1\xe9\x04\x4b\x58\x6d\x26\xcc\x24\x79\x3d\xf2\x0f\x53\x70\xd7\x9d\xc6\x31\xed\x4d\x6e\x55\x59\x07\xab\x1d\xf4\x3a\x73\xb1\x21\xe6\x75\xdd\x2c\x2c\xf3\xf9\x46\xce\x78\xc0\xc4\x60\x60\xb5\x60\x51\x98\xad\x7d\xb3\x4f\x12\x9a\xfd\x96\x33\x0c\xbb\xab\x9b\xed\x07\xb7\xe9\x6f\x66\x8f\x7c\x7d\xad\xeb\xf9\xbe\xde\x90\xf2\xa0\x5a\xdb\x10\x84\x56\x25\xcf\xee\x79\x7d\x75\xcf\x61\xd2\x2d\xac\xb7\x2f\x62\xb2\xe4\xe5\xf2\x3e\x79\xb9\xfc\x97\xfd\x0b\x00\x00\xff\xff\xa5\x5c\xdd\x8c\xe3\x04\x00\x00") 114 | 115 | func viewsIndexHtmlBytes() ([]byte, error) { 116 | return bindataRead( 117 | _viewsIndexHtml, 118 | "views/index.html", 119 | ) 120 | } 121 | 122 | func viewsIndexHtml() (*asset, error) { 123 | bytes, err := viewsIndexHtmlBytes() 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | info := bindataFileInfo{name: "views/index.html", size: 1251, mode: os.FileMode(420), modTime: time.Unix(1510864100, 0)} 129 | a := &asset{bytes: bytes, info: info} 130 | return a, nil 131 | } 132 | 133 | // Asset loads and returns the asset for the given name. 134 | // It returns an error if the asset could not be found or 135 | // could not be loaded. 136 | func Asset(name string) ([]byte, error) { 137 | cannonicalName := strings.Replace(name, "\\", "/", -1) 138 | if f, ok := _bindata[cannonicalName]; ok { 139 | a, err := f() 140 | if err != nil { 141 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 142 | } 143 | return a.bytes, nil 144 | } 145 | return nil, fmt.Errorf("Asset %s not found", name) 146 | } 147 | 148 | // MustAsset is like Asset but panics when Asset would return an error. 149 | // It simplifies safe initialization of global variables. 150 | func MustAsset(name string) []byte { 151 | a, err := Asset(name) 152 | if err != nil { 153 | panic("asset: Asset(" + name + "): " + err.Error()) 154 | } 155 | 156 | return a 157 | } 158 | 159 | // AssetInfo loads and returns the asset info for the given name. 160 | // It returns an error if the asset could not be found or 161 | // could not be loaded. 162 | func AssetInfo(name string) (os.FileInfo, error) { 163 | cannonicalName := strings.Replace(name, "\\", "/", -1) 164 | if f, ok := _bindata[cannonicalName]; ok { 165 | a, err := f() 166 | if err != nil { 167 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 168 | } 169 | return a.info, nil 170 | } 171 | return nil, fmt.Errorf("AssetInfo %s not found", name) 172 | } 173 | 174 | // AssetNames returns the names of the assets. 175 | func AssetNames() []string { 176 | names := make([]string, 0, len(_bindata)) 177 | for name := range _bindata { 178 | names = append(names, name) 179 | } 180 | return names 181 | } 182 | 183 | // _bindata is a table, holding each asset generator, mapped to its name. 184 | var _bindata = map[string]func() (*asset, error){ 185 | "css/index.css": cssIndexCss, 186 | "js/index.js": jsIndexJs, 187 | "views/index.html": viewsIndexHtml, 188 | } 189 | 190 | // AssetDir returns the file names below a certain 191 | // directory embedded in the file by go-bindata. 192 | // For example if you run go-bindata on data/... and data contains the 193 | // following hierarchy: 194 | // data/ 195 | // foo.txt 196 | // img/ 197 | // a.png 198 | // b.png 199 | // then AssetDir("data") would return []string{"foo.txt", "img"} 200 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 201 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 202 | // AssetDir("") will return []string{"data"}. 203 | func AssetDir(name string) ([]string, error) { 204 | node := _bintree 205 | if len(name) != 0 { 206 | cannonicalName := strings.Replace(name, "\\", "/", -1) 207 | pathList := strings.Split(cannonicalName, "/") 208 | for _, p := range pathList { 209 | node = node.Children[p] 210 | if node == nil { 211 | return nil, fmt.Errorf("Asset %s not found", name) 212 | } 213 | } 214 | } 215 | if node.Func != nil { 216 | return nil, fmt.Errorf("Asset %s not found", name) 217 | } 218 | rv := make([]string, 0, len(node.Children)) 219 | for childName := range node.Children { 220 | rv = append(rv, childName) 221 | } 222 | return rv, nil 223 | } 224 | 225 | type bintree struct { 226 | Func func() (*asset, error) 227 | Children map[string]*bintree 228 | } 229 | var _bintree = &bintree{nil, map[string]*bintree{ 230 | "css": &bintree{nil, map[string]*bintree{ 231 | "index.css": &bintree{cssIndexCss, map[string]*bintree{}}, 232 | }}, 233 | "js": &bintree{nil, map[string]*bintree{ 234 | "index.js": &bintree{jsIndexJs, map[string]*bintree{}}, 235 | }}, 236 | "views": &bintree{nil, map[string]*bintree{ 237 | "index.html": &bintree{viewsIndexHtml, map[string]*bintree{}}, 238 | }}, 239 | }} 240 | 241 | // RestoreAsset restores an asset under the given directory 242 | func RestoreAsset(dir, name string) error { 243 | data, err := Asset(name) 244 | if err != nil { 245 | return err 246 | } 247 | info, err := AssetInfo(name) 248 | if err != nil { 249 | return err 250 | } 251 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 252 | if err != nil { 253 | return err 254 | } 255 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 256 | if err != nil { 257 | return err 258 | } 259 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 260 | if err != nil { 261 | return err 262 | } 263 | return nil 264 | } 265 | 266 | // RestoreAssets restores an asset under the given directory recursively 267 | func RestoreAssets(dir, name string) error { 268 | children, err := AssetDir(name) 269 | // File 270 | if err != nil { 271 | return RestoreAsset(dir, name) 272 | } 273 | // Dir 274 | for _, child := range children { 275 | err = RestoreAssets(dir, filepath.Join(name, child)) 276 | if err != nil { 277 | return err 278 | } 279 | } 280 | return nil 281 | } 282 | 283 | func _filePath(dir, name string) string { 284 | cannonicalName := strings.Replace(name, "\\", "/", -1) 285 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 286 | } 287 | 288 | -------------------------------------------------------------------------------- /docs/themes/apex/doc.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -modtime 0 -pkg apex -prefix files files/... 2 | 3 | package apex 4 | -------------------------------------------------------------------------------- /docs/themes/apex/files/css/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ease: cubic-bezier(.82, 0, .12, 1); 3 | --width: 800px; 4 | --header-height: 400px; 5 | 6 | --tracking: 0.05rem; 7 | --tracking-medium: 0.5rem; 8 | --tracking-large: 0.8rem; 9 | 10 | --dark: #000; 11 | --blue: #33A9FF; 12 | --light-gray: #fafafa; 13 | 14 | --bg: #fff; 15 | --fg: #868E96; 16 | --fg-dark: #212529; 17 | --grape: #5f3dc4; 18 | 19 | --selection-bg: var(--blue); 20 | --selection-fg: white; 21 | } 22 | 23 | * { 24 | box-sizing: border-box; 25 | } 26 | 27 | html, body { 28 | font-family: -apple-system, BlinkMacSystemFont, 29 | "Segoe UI", "Roboto", "Oxygen", 30 | "Ubuntu", "Cantarell", "Fira Sans", 31 | "Droid Sans", "Helvetica Neue", sans-serif; 32 | font-size: 15px; 33 | font-weight: 300; 34 | background: var(--bg); 35 | color: var(--fg-dark); 36 | line-height: 1; 37 | margin: 0; 38 | padding: 0; 39 | } 40 | 41 | ::selection { 42 | background: var(--selection-bg); 43 | color: var(--selection-fg); 44 | } 45 | 46 | h1, h2, h3, h4 { 47 | margin-top: 75px; 48 | margin-bottom: 0; 49 | font-size: 1.2rem; 50 | font-weight: 600; 51 | line-height: 1.5rem; 52 | color: var(--fg-dark); 53 | } 54 | 55 | h1 { 56 | margin-top: 0; 57 | font-size: 1.5rem; 58 | } 59 | 60 | h1 + p { 61 | font-size: 1.5rem; 62 | line-height: 1.6; 63 | } 64 | 65 | a { 66 | color: inherit; 67 | text-decoration: none; 68 | } 69 | 70 | p { 71 | margin: 25px 0; 72 | line-height: 1.75; 73 | color: var(--fg-color-light); 74 | } 75 | 76 | li strong, 77 | p strong { 78 | color: var(--fg-dark); 79 | font-weight: 500; 80 | } 81 | 82 | li a, 83 | p a { 84 | color: var(--fg-dark); 85 | font-weight: 400; 86 | padding-bottom: 3px; 87 | border-bottom: 1px dotted #ddd; 88 | } 89 | 90 | li a:hover, 91 | p a:hover { 92 | color: var(--blue); 93 | border-bottom: none; 94 | } 95 | 96 | p a:hover { 97 | border-bottom-color: var(--color); 98 | } 99 | 100 | ul { 101 | margin: 50px 0 50px 30px; 102 | padding: 0; 103 | } 104 | 105 | ul ul { 106 | margin: 10px 0 10px 30px; 107 | } 108 | 109 | ul li { 110 | margin: 5px 0; 111 | color: var(--fg-color-light); 112 | line-height: 1.5em; 113 | } 114 | 115 | ul li strong { 116 | color: var(--fg-color); 117 | font-weight: 500; 118 | } 119 | 120 | pre { 121 | background: var(--light-gray); 122 | color: var(--dark); 123 | padding: 30px; 124 | border-radius: 2px; 125 | overflow-x: auto; 126 | font: "Source Code Pro", Menlo, monospace; 127 | font-size: .8em; 128 | line-height: 1.5em; 129 | -webkit-font-smoothing: antialiased; 130 | } 131 | 132 | li > code, 133 | p > code { 134 | border: 1px solid #DEE2E6; 135 | font-size: 0.75rem; 136 | padding: 3px 10px; 137 | border-radius: 3px; 138 | white-space: nowrap; 139 | font-weight: 600; 140 | font-family: inherit; 141 | -webkit-font-smoothing: antialiased; 142 | } 143 | 144 | details > summary { 145 | cursor: pointer; 146 | outline: none; 147 | user-select: none; 148 | } 149 | 150 | details > p { 151 | border-left: 3px solid var(--grape); 152 | padding-left: 15px; 153 | } 154 | 155 | .Wrapper { 156 | display: flex; 157 | justify-content: center; 158 | } 159 | 160 | .Container { 161 | width: var(--width); 162 | display: flex; 163 | flex-direction: column; 164 | } 165 | 166 | .Content-wrapper { 167 | display: flex; 168 | } 169 | 170 | .Header { 171 | display: flex; 172 | justify-content: center; 173 | align-items: center; 174 | height: var(--header-height); 175 | } 176 | 177 | .Sidebar { 178 | flex: 1 1 auto; 179 | } 180 | 181 | .Content { 182 | width: 75%; 183 | } 184 | 185 | .Content img { 186 | max-width: 100%; 187 | } 188 | 189 | .Menu { 190 | position: -webkit-sticky; 191 | position: sticky; 192 | top: 50px; 193 | color: var(--fg-dark); 194 | -webkit-font-smoothing: antialiased; 195 | } 196 | 197 | .Menu > .item { 198 | padding-bottom: 15px; 199 | } 200 | 201 | .Menu > .item > a { 202 | position: relative; 203 | user-select: none; 204 | font-weight: 400; 205 | transition: color 200ms; 206 | color: var(--fg); 207 | } 208 | 209 | .Menu > .item > a.active { 210 | color: var(--fg-dark); 211 | } 212 | 213 | .Menu > .item > a:before { 214 | content: ""; 215 | position: absolute; 216 | width: 100%; 217 | height: 1px; 218 | bottom: -5px; 219 | left: 0; 220 | background-color: var(--fg-dark); 221 | visibility: hidden; 222 | transform: scaleX(0); 223 | transform-origin: left center; 224 | transition: all 250ms var(--ease); 225 | } 226 | 227 | .Menu > .item > a:hover { 228 | color: var(--fg-dark); 229 | } 230 | 231 | .Menu > .item > a:hover:before { 232 | visibility: visible; 233 | transform: scaleX(1); 234 | } 235 | 236 | .Page { 237 | margin-top: 100px; 238 | padding-top: 50px; 239 | } 240 | 241 | .Page:first-child { 242 | margin-top: 0; 243 | padding-top: 0; 244 | } 245 | 246 | .Title { 247 | margin: 5px 0; 248 | line-height: 2.2em; 249 | } 250 | 251 | .Title.center { 252 | margin-left: auto; 253 | margin-right: auto; 254 | text-align: center; 255 | max-width: 500px; 256 | } 257 | 258 | .Title.margin { 259 | margin-bottom: 80px; 260 | } 261 | 262 | .Title > span { 263 | display: block; 264 | } 265 | 266 | .Title .subtext { 267 | color: var(--fg-color-light); 268 | font-size: 0.8rem; 269 | text-transform: uppercase; 270 | display: none; 271 | } 272 | 273 | .Title .text { 274 | letter-spacing: var(--tracking); 275 | text-transform: uppercase; 276 | font-weight: bold; 277 | font-size: 16px; 278 | } 279 | 280 | .Title.small .text { 281 | font-size: 14px; 282 | text-transform: none; 283 | letter-spacing: normal; 284 | line-height: 2rem; 285 | } 286 | 287 | .Anchor { 288 | position: relative; 289 | margin-left: -14px; 290 | opacity: 0.15; 291 | display: inline-block; 292 | width: 14px; 293 | height: 14px; 294 | } 295 | 296 | .Anchor svg { 297 | position: absolute; 298 | right: 5px; 299 | top: 0; 300 | } 301 | 302 | .Anchor svg path { 303 | pointer-events: all 304 | } 305 | 306 | .Anchor:hover { 307 | opacity: 1; 308 | } 309 | 310 | .Footer { 311 | height: 100px; 312 | } 313 | 314 | .Message { 315 | margin: 25px 0; 316 | padding: 25px; 317 | background: #e7f5ff; 318 | border: 1px solid #74c0fc; 319 | border-radius: 3px; 320 | color: #1c7ed6; 321 | } 322 | 323 | .Message p { 324 | margin: 0; 325 | } 326 | 327 | @media screen and (max-width: 850px) { 328 | html, body { 329 | --header-height: 300px; 330 | --width: 80%; 331 | font-size: 14px; 332 | } 333 | 334 | .Content-wrapper { 335 | display: flex; 336 | flex-direction: column; 337 | } 338 | 339 | .Sidebar { 340 | margin-bottom: 30px; 341 | } 342 | 343 | .Content { 344 | width: 100%; 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /docs/themes/apex/files/js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Setup. 3 | */ 4 | 5 | const items = document.querySelectorAll('.Page') 6 | const links = document.querySelectorAll('.Menu a') 7 | 8 | /** 9 | * Check if `el` is out out of view. 10 | */ 11 | 12 | function isBelowScroll(el) { 13 | return el.getBoundingClientRect().bottom > 0 14 | } 15 | 16 | /** 17 | * Activate item `i`. 18 | */ 19 | 20 | function activateItem(i) { 21 | links.forEach(e => e.classList.remove('active')) 22 | links[i].classList.add('active') 23 | } 24 | 25 | /** 26 | * Activate the correct menu item for the 27 | * contents in the viewport. 28 | */ 29 | 30 | function activate() { 31 | let i = 0 32 | 33 | for (; i < items.length; i++) { 34 | if (isBelowScroll(items[i])) { 35 | break 36 | } 37 | } 38 | 39 | activateItem(i) 40 | } 41 | 42 | /** 43 | * Activate scroll spy thingy. 44 | */ 45 | 46 | window.addEventListener('scroll', e => activate()) 47 | -------------------------------------------------------------------------------- /docs/themes/apex/files/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{.Title}} | {{.Subtitle}} 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | {{.Title}} 16 | {{.Subtitle}} 17 |
18 |
19 | 20 |
21 | 30 | 31 |
32 | {{range .Pages}} 33 |
34 |

{{.Title}}

35 | {{.Content}} 36 |
37 | {{end}} 38 | 39 | 40 |
41 |
42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /frontmatter.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | 7 | "github.com/tj/front" 8 | ) 9 | 10 | // Frontmatter returns a reader of contents and unmarshals frontmatter to v. 11 | func Frontmatter(r io.Reader, v interface{}) io.ReadCloser { 12 | pr, pw := io.Pipe() 13 | 14 | go func() { 15 | b, err := ioutil.ReadAll(r) 16 | if err != nil { 17 | pw.CloseWithError(err) 18 | return 19 | } 20 | 21 | s, err := front.Unmarshal(b, v) 22 | if err != nil { 23 | pw.CloseWithError(err) 24 | return 25 | } 26 | 27 | pw.Write(s) 28 | pw.Close() 29 | }() 30 | 31 | return pr 32 | } 33 | -------------------------------------------------------------------------------- /frontmatter_test.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/tj/assert" 9 | ) 10 | 11 | func TestFrontmatter(t *testing.T) { 12 | t.Run("valid", func(t *testing.T) { 13 | in := strings.NewReader(`--- 14 | title: Something 15 | --- 16 | 17 | Hello __World__. 18 | `) 19 | 20 | var meta struct { 21 | Title string `yml:"title"` 22 | } 23 | 24 | out := Markdown(Frontmatter(in, &meta)) 25 | b, err := ioutil.ReadAll(out) 26 | assert.NoError(t, err, "reading") 27 | assert.NoError(t, out.Close()) 28 | assert.Equal(t, "

Hello World.

\n", string(b)) 29 | }) 30 | 31 | t.Run("read error", func(t *testing.T) { 32 | in := &errorReader{} 33 | 34 | var meta struct { 35 | Title string `yml:"title"` 36 | } 37 | 38 | out := Markdown(Frontmatter(in, &meta)) 39 | 40 | _, err := ioutil.ReadAll(out) 41 | assert.EqualError(t, err, `boom`) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /html.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "strconv" 7 | "strings" 8 | 9 | dom "github.com/PuerkitoBio/goquery" 10 | snakecase "github.com/segmentio/go-snakecase" 11 | ) 12 | 13 | // anchorHTML is the anchor element and SVG icon. 14 | var anchorHTML = `` 17 | 18 | // anchorEl is the instance of the anchor element which is cloned. 19 | var anchorEl *dom.Selection 20 | 21 | // initialize anchor element. 22 | func init() { 23 | doc, err := dom.NewDocumentFromReader(strings.NewReader(anchorHTML)) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | anchorEl = doc.Find("a") 29 | } 30 | 31 | // HeadingAnchors returns a reader with 32 | // HTML heading ids derived from their text. 33 | func HeadingAnchors(r io.Reader) io.ReadCloser { 34 | pr, pw := io.Pipe() 35 | 36 | go func() { 37 | doc, err := dom.NewDocumentFromReader(r) 38 | if err != nil { 39 | pw.CloseWithError(err) 40 | return 41 | } 42 | 43 | scope := []string{""} 44 | prev := 1 45 | 46 | doc.Find("h1, h2, h3, h4, h5, h6").Each(func(i int, s *dom.Selection) { 47 | curr, _ := strconv.Atoi(string(s.Get(0).Data[1])) 48 | change := curr - prev 49 | 50 | if change <= 0 { 51 | scope = scope[:len(scope)+(change-1)] 52 | } 53 | 54 | scope = append(scope, snakecase.Snakecase(s.Text())) 55 | prev = curr 56 | 57 | id := strings.Join(scope, ".") 58 | a := anchorEl.Clone() 59 | a.SetAttr("id", id) 60 | a.SetAttr("href", "#"+id) 61 | s.SetAttr("class", "Heading") 62 | s.PrependSelection(a) 63 | }) 64 | 65 | html, err := doc.Html() 66 | if err != nil { 67 | pw.CloseWithError(err) 68 | return 69 | } 70 | 71 | pw.Write([]byte(html)) 72 | pw.Close() 73 | }() 74 | 75 | return pr 76 | } 77 | 78 | // Notes returns a reader with paragraphs starting 79 | // with "Notes:" to be considered highlighted note. 80 | func Notes(r io.Reader) io.ReadCloser { 81 | pr, pw := io.Pipe() 82 | 83 | go func() { 84 | scan := bufio.NewScanner(r) 85 | 86 | for scan.Scan() { 87 | io.WriteString(pw, note(scan.Text())) 88 | io.WriteString(pw, "\n") 89 | } 90 | 91 | pw.CloseWithError(scan.Err()) 92 | }() 93 | 94 | return pr 95 | } 96 | 97 | // note helper. 98 | func note(s string) string { 99 | if !strings.HasPrefix(s, "

Note:") { 100 | return s 101 | } 102 | s = strings.TrimPrefix(s, "

Note:") 103 | s = strings.TrimSuffix(s, "

") 104 | s = strings.TrimSpace(s) 105 | return `

` + s + `

` 106 | } 107 | -------------------------------------------------------------------------------- /html_highlight.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "strings" 7 | 8 | dom "github.com/PuerkitoBio/goquery" 9 | "github.com/alecthomas/chroma" 10 | "github.com/alecthomas/chroma/formatters/html" 11 | "github.com/alecthomas/chroma/lexers" 12 | ) 13 | 14 | // blankStyle is a blank style. 15 | var blankStyle = chroma.MustNewStyle("blank", chroma.StyleEntries{}) 16 | 17 | // SyntaxHighlight returns a reader with HTML code prettified with chroma. 18 | func SyntaxHighlight(r io.Reader) io.ReadCloser { 19 | pr, pw := io.Pipe() 20 | 21 | go func() { 22 | doc, err := dom.NewDocumentFromReader(r) 23 | if err != nil { 24 | pw.CloseWithError(err) 25 | return 26 | } 27 | 28 | formatter := html.New(html.WithClasses(true)) 29 | doc.Find("pre > code").Each(func(i int, s *dom.Selection) { 30 | lexer := detectLexer(s) 31 | code := s.Contents().Text() 32 | 33 | iterator, err := lexer.Tokenise(nil, code) 34 | if err != nil { 35 | pw.CloseWithError(err) 36 | return 37 | } 38 | 39 | var buf bytes.Buffer 40 | err = formatter.Format(&buf, blankStyle, iterator) 41 | if err != nil { 42 | pw.CloseWithError(err) 43 | return 44 | } 45 | 46 | // Parent() because chroma html is already in a
.
47 | 			s.Parent().ReplaceWithHtml(buf.String())
48 | 		})
49 | 
50 | 		html, err := doc.Html()
51 | 		if err != nil {
52 | 			pw.CloseWithError(err)
53 | 			return
54 | 		}
55 | 
56 | 		pw.Write([]byte(html))
57 | 		pw.Close()
58 | 	}()
59 | 
60 | 	return pr
61 | }
62 | 
63 | // detectLexer returns a chroma lexer based on the classname.
64 | func detectLexer(s *dom.Selection) chroma.Lexer {
65 | 	var lexer chroma.Lexer
66 | 	var lang string
67 | 
68 | 	if classes, ok := s.Attr("class"); ok {
69 | 		for _, c := range strings.Split(classes, " ") {
70 | 			if strings.HasPrefix(c, "language-") {
71 | 				lang = strings.TrimPrefix(c, "language-")
72 | 			}
73 | 		}
74 | 	}
75 | 
76 | 	if lang != "" {
77 | 		lexer = lexers.Get(lang)
78 | 	}
79 | 
80 | 	if lexer == nil {
81 | 		lexer = lexers.Analyse(s.Contents().Text())
82 | 	}
83 | 
84 | 	if lexer == nil {
85 | 		lexer = lexers.Fallback
86 | 	}
87 | 
88 | 	return chroma.Coalesce(lexer)
89 | }
90 | 


--------------------------------------------------------------------------------
/html_highlight_test.go:
--------------------------------------------------------------------------------
 1 | package static
 2 | 
 3 | import (
 4 | 	"io"
 5 | 	"io/ioutil"
 6 | 	"os"
 7 | 	"path/filepath"
 8 | 	"testing"
 9 | 
10 | 	"github.com/tj/assert"
11 | )
12 | 
13 | func fixture(t testing.TB, name string) io.ReadCloser {
14 | 	path := filepath.Join("testdata", name)
15 | 	f, err := os.Open(path)
16 | 	assert.NoError(t, err, "open")
17 | 	return f
18 | }
19 | 
20 | func TestSyntaxHighlight(t *testing.T) {
21 | 	in := fixture(t, "highlight_input.html")
22 | 	out := fixture(t, "highlight_output.html")
23 | 
24 | 	got, _ := ioutil.ReadAll(SyntaxHighlight(in))
25 | 	expect, _ := ioutil.ReadAll(out)
26 | 
27 | 	if string(got) != string(expect) {
28 | 		t.Errorf("\nExpected:\n\n%s\n\nGot:\n\n%s", string(expect), string(got))
29 | 		// ioutil.WriteFile("testdata/highlight_output.html", got, 0644)
30 | 	}
31 | }
32 | 


--------------------------------------------------------------------------------
/html_test.go:
--------------------------------------------------------------------------------
 1 | package static
 2 | 
 3 | import (
 4 | 	"io"
 5 | 	"io/ioutil"
 6 | 	"os"
 7 | 	"strings"
 8 | )
 9 | 
10 | func ExampleHeadingAnchors() {
11 | 	r := ioutil.NopCloser(strings.NewReader(`
12 | 		

Article 1

13 |

Section 1

14 |

Section 2

15 |

Section 3

16 |

Sub Section 1

17 |

Sub Sub Section 1

18 |

Article 2

19 |

Article 3

20 |

Section 1

21 | `)) 22 | 23 | r = HeadingAnchors(r) 24 | io.Copy(os.Stdout, r) 25 | // Output: 26 | //

Article 1

29 | //

Section 1

32 | //

Section 2

35 | //

Section 3

38 | //

Sub Section 1

41 | //

Sub Sub Section 1

44 | //

Article 2

47 | //

Article 3

50 | //

Section 1

53 | // 54 | } 55 | 56 | func ExampleNotes() { 57 | r := ioutil.NopCloser(strings.NewReader(` 58 | Something here. 59 | 60 | Note: Some note here about whatever nothing exciting. 61 | 62 | More content. 63 | `)) 64 | 65 | r = Notes(Markdown(r)) 66 | io.Copy(os.Stdout, r) 67 | // Output: 68 | //

Something here.

69 | // 70 | //

Some note here about whatever nothing exciting.

71 | // 72 | //

More content.

73 | } 74 | -------------------------------------------------------------------------------- /inject/inject.go: -------------------------------------------------------------------------------- 1 | // Package inject provides script and style injection utilities. 2 | package inject 3 | 4 | import ( 5 | "encoding/json" 6 | "html" 7 | "strings" 8 | ) 9 | 10 | // Head injects a string before the closing head tag. 11 | func Head(html, s string) string { 12 | return strings.Replace(html, "", " "+s+"\n ", 1) 13 | } 14 | 15 | // Body injects a string before the closing body tag. 16 | func Body(html, s string) string { 17 | return strings.Replace(html, "", " "+s+"\n ", 1) 18 | } 19 | 20 | // Script returns an script. 21 | func Script(src string) string { 22 | return `` 23 | } 24 | 25 | // ScriptInline returns an inline script. 26 | func ScriptInline(s string) string { 27 | return `` 28 | } 29 | 30 | // Style returns an style. 31 | func Style(href string) string { 32 | return `` 33 | } 34 | 35 | // StyleInline returns an inline style. 36 | func StyleInline(s string) string { 37 | return `` 38 | } 39 | 40 | // Comment returns an html comment. 41 | func Comment(s string) string { 42 | return "" 43 | } 44 | 45 | // Segment inline script with key. 46 | func Segment(key string) string { 47 | return ScriptInline(` 48 | !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t 10 | 11 | 12 | 13 | Example 14 | 15 | 16 |

Hello World

17 | 18 | 19 | ` 20 | 21 | func ExampleStyle() { 22 | fmt.Printf("%s\n", inject.Style(`/sloth.css`)) 23 | // Output: 24 | // 25 | } 26 | 27 | func ExampleStyleInline() { 28 | fmt.Printf("%s\n", inject.StyleInline(`body { display: none }`)) 29 | // Output: 30 | // 31 | } 32 | 33 | func ExampleScript() { 34 | fmt.Printf("%s\n", inject.Script(`/sloth.js`)) 35 | // Output: 36 | // 37 | } 38 | 39 | func ExampleScriptInline() { 40 | fmt.Printf("%s\n", inject.ScriptInline(`const user = { "name": "Tobi" }`)) 41 | // Output: 42 | // 43 | } 44 | 45 | func ExampleComment() { 46 | fmt.Printf("%s\n", inject.Comment(`Hello World`)) 47 | // Output: 48 | // 49 | } 50 | 51 | func ExampleHead() { 52 | s := inject.Head(html, ``) 53 | fmt.Printf("%s\n", s) 54 | // Output: 55 | // 56 | // 57 | // 58 | // 59 | // Example 60 | // 61 | // 62 | // 63 | //

Hello World

64 | // 65 | // 66 | } 67 | 68 | func ExampleBody() { 69 | s := inject.Body(html, inject.Comment("Version 1.0.3")) 70 | fmt.Printf("%s\n", s) 71 | // Output: 72 | // 73 | // 74 | // 75 | // 76 | // Example 77 | // 78 | // 79 | //

Hello World

80 | // 81 | // 82 | // 83 | } 84 | 85 | func ExampleSegment() { 86 | fmt.Printf("%s\n", inject.Segment(`KEY HERE`)) 87 | // Output: 88 | // 94 | } 95 | 96 | func ExampleGoogleAnalytics() { 97 | fmt.Printf("%s\n", inject.GoogleAnalytics(`KEY HERE`)) 98 | // Output: 99 | // 108 | } 109 | 110 | func ExampleVar() { 111 | user := map[string]string{ 112 | "name": "Tobi", 113 | } 114 | 115 | fmt.Printf("%s\n", inject.Var("const", "user", user)) 116 | // Output: 117 | // 118 | } 119 | -------------------------------------------------------------------------------- /markdown.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | 7 | "github.com/russross/blackfriday" 8 | ) 9 | 10 | // Markdown returns a reader transformed to markdown. 11 | func Markdown(r io.Reader) io.ReadCloser { 12 | pr, pw := io.Pipe() 13 | 14 | go func() { 15 | b, err := ioutil.ReadAll(r) 16 | if err != nil { 17 | pw.CloseWithError(err) 18 | return 19 | } 20 | 21 | pw.Write(blackfriday.MarkdownCommon(b)) 22 | pw.Close() 23 | }() 24 | 25 | return pr 26 | } 27 | -------------------------------------------------------------------------------- /markdown_test.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/tj/assert" 12 | ) 13 | 14 | type errorReader struct{} 15 | 16 | func (r *errorReader) Read(b []byte) (int, error) { 17 | return 0, errors.New("boom") 18 | } 19 | 20 | func ExampleMarkdown() { 21 | r := Markdown(strings.NewReader(`Hello __World__.`)) 22 | io.Copy(os.Stdout, r) 23 | // Output: 24 | //

Hello World.

25 | } 26 | 27 | func TestMarkdown(t *testing.T) { 28 | t.Run("valid", func(t *testing.T) { 29 | in := strings.NewReader(`Hello __World__.`) 30 | out := Markdown(in) 31 | b, err := ioutil.ReadAll(out) 32 | assert.NoError(t, err, "reading") 33 | assert.NoError(t, out.Close()) 34 | assert.Equal(t, "

Hello World.

\n", string(b)) 35 | }) 36 | 37 | t.Run("read error", func(t *testing.T) { 38 | in := &errorReader{} 39 | out := Markdown(in) 40 | _, err := ioutil.ReadAll(out) 41 | assert.EqualError(t, err, `boom`) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /testdata/highlight_input.html: -------------------------------------------------------------------------------- 1 |

 2 | package main
 3 | 
 4 | func main() {
 5 | 	fmt.Println("foo")
 6 | }
 7 | 
8 | 9 |

10 | foo: bar
11 | list:
12 | - 1
13 | - 2
14 | 
15 | 16 |

17 | this is not even a lang
18 | 
19 | -------------------------------------------------------------------------------- /testdata/highlight_output.html: -------------------------------------------------------------------------------- 1 |
package main
 2 | 
 3 | func main() {
 4 | 	fmt.Println("foo")
 5 | }
 6 | 
7 | 8 |

 9 | foo: bar
10 | list:
11 | - 1
12 | - 2
13 | 
14 | 15 |
this is not even a lang
16 | 
17 | --------------------------------------------------------------------------------