├── .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 | [](https://godoc.org/github.com/apex/static)
26 | 
27 | 
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 |
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 + `
.
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 | //
29 | // Article 1
32 | // Section 1
35 | // Section 2
38 | // Section 3
41 | // Sub Section 1
44 | // Sub Sub Section 1
47 | // Article 2
50 | // Article 3
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 | // Section 1Something 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, "