├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── cmd
├── herdyo
│ └── herdyo.go
└── tomlo
│ └── tomlo.go
├── config
├── config.go
├── defaults.go
└── http.go
├── docker-compose.yml
├── encrypt
├── encrypt.go
└── encrypt_test.go
├── go.mod
├── go.sum
├── main.go
├── multisite_sample.toml
├── server
├── bindata-debug.go
├── bindata.go
├── debug.go
├── handlers.go
├── listify.go
├── migrate.go
├── page.go
├── page_test.go
├── utils.go
└── utils_test.go
├── static
├── css
│ ├── base-min.css
│ ├── default.css
│ ├── dropzone.css
│ ├── github-markdown.css
│ ├── highlight.css
│ └── menus-min.css
├── img
│ ├── cowyo
│ │ ├── android-icon-144x144.png
│ │ ├── android-icon-192x192.png
│ │ ├── android-icon-36x36.png
│ │ ├── android-icon-48x48.png
│ │ ├── android-icon-72x72.png
│ │ ├── android-icon-96x96.png
│ │ ├── apple-icon-114x114.png
│ │ ├── apple-icon-120x120.png
│ │ ├── apple-icon-144x144.png
│ │ ├── apple-icon-152x152.png
│ │ ├── apple-icon-180x180.png
│ │ ├── apple-icon-57x57.png
│ │ ├── apple-icon-60x60.png
│ │ ├── apple-icon-72x72.png
│ │ ├── apple-icon-76x76.png
│ │ ├── apple-icon-precomposed.png
│ │ ├── apple-icon.png
│ │ ├── browserconfig.xml
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon-96x96.png
│ │ ├── favicon.ico
│ │ ├── manifest.json
│ │ ├── ms-icon-144x144.png
│ │ ├── ms-icon-150x150.png
│ │ ├── ms-icon-310x310.png
│ │ └── ms-icon-70x70.png
│ └── logo.png
├── js
│ ├── cowyo.js
│ ├── dropzone.js
│ ├── highlight.min.js
│ ├── highlight.pack.js
│ └── jquery-1.8.3.js
└── text
│ ├── adjectives
│ ├── adjectives.old
│ ├── animals
│ ├── animals.all
│ ├── howmany.py
│ ├── robots.txt
│ └── sitemap.xml
└── templates
└── index.tmpl
/.gitattributes:
--------------------------------------------------------------------------------
1 | static/* linguist-vendored
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: schollz
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
26 | data/*
27 | cowyo
28 | dist
29 |
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - tip
5 |
6 | before_install:
7 | - go get -u github.com/schollz/cowyo
8 | - go get -u github.com/jteeuwen/go-bindata/...
9 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.12-alpine as builder
2 | RUN apk add --no-cache git make
3 | RUN go get -v github.com/jteeuwen/go-bindata/go-bindata
4 | WORKDIR /go/cowyo
5 | COPY . .
6 | RUN make build
7 |
8 | FROM alpine:latest
9 | VOLUME /data
10 | EXPOSE 8050
11 | COPY --from=builder /go/cowyo/cowyo /cowyo
12 | ENTRYPOINT ["/cowyo"]
13 | CMD ["--data","/data","--allow-file-uploads","--max-upload-mb","10","--host","0.0.0.0"]
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Zack
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Make a release with
2 | # make -j4 release
3 |
4 | VERSION=$(shell git describe)
5 | LDFLAGS=-ldflags "-X main.version=${VERSION}"
6 |
7 | .PHONY: build
8 | build: server/bindata.go
9 | go build ${LDFLAGS}
10 |
11 | STATICFILES := $(wildcard static/*)
12 | TEMPLATES := $(wildcard templates/*)
13 | server/bindata.go: $(STATICFILES) $(TEMPLATES)
14 | go-bindata -pkg server -tags '!debug' -o server/bindata.go static/... templates/...
15 | go fmt
16 |
17 | server/bindata-debug.go: $(STATICFILES) $(TEMPLATES)
18 | go-bindata -pkg server -tags 'debug' -o server/bindata-debug.go -debug static/... templates/...
19 | go fmt
20 |
21 | .PHONY: devel
22 | devel: server/bindata-debug.go
23 | go build -tags debug
24 |
25 | .PHONY: quick
26 | quick: server/bindata.go
27 | go build
28 |
29 | .PHONY: linuxarm
30 | linuxarm: server/bindata.go
31 | env GOOS=linux GOARCH=arm go build ${LDFLAGS} -o dist/cowyo_linux_arm
32 | #cd dist && upx --brute cowyo_linux_arm
33 |
34 | .PHONY: linux32
35 | linux32: server/bindata.go
36 | env GOOS=linux GOARCH=386 go build ${LDFLAGS} -o dist/cowyo_linux_32bit
37 | #cd dist && upx --brute cowyo_linux_32bit
38 |
39 | .PHONY: linux64
40 | linux64: server/bindata.go
41 | env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_linux_amd64
42 |
43 | .PHONY: windows
44 | windows: server/bindata.go
45 | env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_windows_amd64.exe
46 | #cd dist && upx --brute cowyo_windows_amd64.exe
47 |
48 | .PHONY: osx
49 | osx: server/bindata.go
50 | env GOOS=darwin GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_osx_amd64
51 | #cd dist && upx --brute cowyo_osx_amd64
52 |
53 | .PHONY: release
54 | release: osx windows linux64 linux32 linuxarm
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
13 |
14 | A feature-rich wiki for minimalists
15 |
16 | *cowyo* is a self-contained wiki server that makes jotting notes easy and _fast_. The most important feature here is _simplicity_. Other features include versioning, page locking, self-destructing messages, encryption, and listifying. You can [download *cowyo* as a single executable](https://github.com/schollz/cowyo/releases/latest) or install it with Go. Try it out at https://cowyo.com.
17 |
18 | There is now [a command-line tool, *cowyodel*](https://github.com/schollz/cowyodel) to interact with *cowyo* and transfer information between computers with only a code phrase: [schollz/cowyodel](https://github.com/schollz/cowyodel).
19 |
20 | Getting Started
21 | ===============
22 |
23 | ## Install
24 |
25 | If you have go
26 |
27 | ```
28 | go get -u github.com/schollz/cowyo/...
29 | ```
30 |
31 | or just [download the latest release](https://github.com/schollz/cowyo/releases/latest).
32 |
33 | ## Run
34 |
35 | To run just double click or from the command line:
36 |
37 | ```
38 | cowyo
39 | ```
40 |
41 | and it will start a server listening on `0.0.0.0:8050`. To view it, just go to http://localhost:8050 (the server prints out the local IP for your info if you want to do LAN networking). You can change the port with `-port X`, and you can listen *only* on localhost using `-host localhost`.
42 |
43 | **Running with TLS**
44 |
45 | Specify a matching pair of SSL Certificate and Key to run cowyo using https. *cowyo* will now run in a secure session.
46 |
47 | *N.B. Let's Encrypt is a CA that signs free and signed certificates.*
48 |
49 | ```
50 | cowyo --cert "/path/to/server.crt" --key "/p/t/server.key"
51 | ```
52 |
53 | **Running with Docker**
54 |
55 | You can easily get started with Docker. First pull the latest image and create the volume with:
56 |
57 | ```
58 | docker run -d -v /directory/to/data:/data -p 8050:8050 schollz/cowyo
59 | ```
60 |
61 | Then you can stop it with `docker stop cowyo` and start it again with `docker start cowyo`.
62 |
63 | ## Server customization
64 |
65 | There are a couple of command-line flags that you can use to make *cowyo* your own micro-CMS.
66 |
67 | ```
68 | cowyo -lock 123 -default-page index.html -css mystyle.css -diary
69 | ```
70 |
71 | The `-lock` flag will automatically lock every page with the passphrase "123". Also, the default behavior will be to redirect `/` to `/index.html`. Also, every page that is published will automatically redirect to `/mypage/read` which will show the custom CSS file if it is supplied with `-css`. The `-diary` flag allows you to generate a time-stamped page instead of a random named page when you select "New".
72 |
73 | ## Usage
74 |
75 | *cowyo* is straightforward to use. Here are some of the basic features:
76 |
77 | ### Publishing
78 |
79 | If you hover the the top left button (the name of the page) you will see the option "Publish". Publishing will add the page to the `sitemap.xml` for crawlers to find. It will also default that page to go to the `/read` route so it can be easily viewed as a single page.
80 |
81 | ### View all the pages
82 |
83 | To view the current list of all the pages goto to `/ls`.
84 |
85 | ### Editing
86 |
87 | When you open a document you'll be directed to an alliterative animal (which is supposed to be easy to remember). You can write in Markdown. Saving is performed as soon as you stop writing. You can easily link pages using [[PageName]] as you edit.
88 |
89 | 
90 |
91 | ### History
92 |
93 | You can easily see previous versions of your documents.
94 |
95 | 
96 |
97 | ### Lists
98 |
99 | You can easily make lists and check them off.
100 |
101 | 
102 |
103 | ### Locking
104 |
105 | Locking prevents other users from editing your pages without a passphrase.
106 |
107 | 
108 |
109 | ### Encryption
110 |
111 | Encryption is performed using AES-256.
112 |
113 | 
114 |
115 | ### Self-destructing pages
116 |
117 | Just like in mission impossible.
118 |
119 | 
120 |
121 |
122 | ## Development
123 |
124 | You can run the tests using
125 |
126 | ```
127 | $ cd $GOPATH/src/github.com/schollz/cowyo
128 | $ go test ./...
129 | ```
130 |
131 | Any contributions are welcome.
132 |
133 | ## Thanks
134 |
135 | ...to [DanielHeath](https://github.com/DanielHeath) who has introduced some stellar improvements into cowyo including supporting category pages, hot-template reloading, preventing out-of-order updates, added password access, fade-out on deleting list items, and image upload support!
136 |
137 | ## License
138 |
139 | MIT
140 |
141 |
--------------------------------------------------------------------------------
/cmd/herdyo/herdyo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 |
7 | "github.com/gin-contrib/sessions"
8 | "github.com/schollz/cowyo/server"
9 | )
10 |
11 | func main() {
12 | store := sessions.NewStore([]byte("secret"))
13 |
14 | first := server.Site{
15 | PathToData: "site1",
16 | Debounce: 500,
17 | SessionStore: store,
18 | AllowInsecure: true,
19 | Fileuploads: true,
20 | MaxUploadSize: 2,
21 | }.Router()
22 |
23 | second := server.Site{
24 | PathToData: "site2",
25 | Debounce: 500,
26 | SessionStore: store,
27 | AllowInsecure: true,
28 | Fileuploads: true,
29 | MaxUploadSize: 2,
30 | }.Router()
31 | panic(http.ListenAndServe("localhost:8000", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
32 | if strings.HasPrefix(r.Host, "first") {
33 | first.ServeHTTP(rw, r)
34 | } else if strings.HasPrefix(r.Host, "second") {
35 | second.ServeHTTP(rw, r)
36 | } else {
37 | http.NotFound(rw, r)
38 | }
39 | })))
40 | }
41 |
--------------------------------------------------------------------------------
/cmd/tomlo/tomlo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/schollz/cowyo/config"
5 | )
6 |
7 | func main() {
8 | c, err := config.ParseFile("multisite_sample.toml")
9 | if err != nil {
10 | panic(err)
11 | }
12 |
13 | panic(c.ListenAndServe())
14 | }
15 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/BurntSushi/toml"
5 | )
6 |
7 | func ParseFile(path string) (Config, error) {
8 | c := Config{}
9 | if _, err := toml.DecodeFile("multisite_sample.toml", &c); err != nil {
10 | // handle error
11 | return c, err
12 | }
13 | c.SetDefaults()
14 | c.Validate()
15 | return c, nil
16 | }
17 |
18 | type Config struct {
19 | Default SiteConfig
20 | Sites []SiteConfig
21 | }
22 |
23 | type SiteConfig struct {
24 | Host *string
25 | Port *int
26 | DataDir *string
27 | DefaultPage *string
28 | AllowInsecureMarkup *bool
29 | Lock *string
30 | DebounceSave *int
31 | Diary *bool
32 | AccessCode *string
33 | FileUploadsAllowed *bool
34 | MaxFileUploadMb *uint
35 | MaxDocumentLength *uint
36 | TLS *TLSConfig
37 | CookieKeys []CookieKey
38 | }
39 |
40 | type TLSConfig struct {
41 | CertPath string
42 | KeyPath string
43 | Port int
44 | }
45 |
46 | type CookieKey struct {
47 | AuthenticateBase64 string
48 | EncryptBase64 string
49 | }
50 |
51 | func (c Config) Validate() {
52 | for _, v := range c.Sites {
53 | v.sessionStore()
54 | }
55 | }
--------------------------------------------------------------------------------
/config/defaults.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "encoding/base64"
5 | "crypto/rand"
6 | )
7 | var DefaultSiteConfig SiteConfig
8 |
9 |
10 | func makeAuthKey() string {
11 | secret := make([]byte, 32)
12 | _, err := rand.Read(secret)
13 | if err != nil {
14 | panic(err)
15 | }
16 | return base64.StdEncoding.EncodeToString(secret)
17 | }
18 |
19 | func init() {
20 | host := "*"
21 | port := 8050
22 | debounce := 500
23 | dataDir := "data"
24 | empty := ""
25 | zer := uint(0)
26 | lots := uint(100000000)
27 | fal := false
28 |
29 | ck := CookieKey{
30 | AuthenticateBase64: "",
31 | EncryptBase64: "",
32 | }
33 |
34 | DefaultSiteConfig = SiteConfig{
35 | Host:&host,
36 | Port:&port,
37 | DataDir:&dataDir,
38 | DebounceSave:&debounce,
39 | CookieKeys: []CookieKey{ck},
40 | DefaultPage:&empty,
41 | AllowInsecureMarkup:&fal,
42 | Lock:&empty,
43 | Diary:&fal,
44 | AccessCode:&empty,
45 | FileUploadsAllowed:&fal,
46 | MaxFileUploadMb:&zer,
47 | MaxDocumentLength:&lots,
48 | }
49 | }
50 |
51 |
52 | func copyDefaults(base, defaults *SiteConfig) {
53 | if base.Host == nil {
54 | base.Host = defaults.Host
55 | }
56 | if base.Port == nil {
57 | base.Port = defaults.Port
58 | }
59 | if base.DataDir == nil {
60 | base.DataDir = defaults.DataDir
61 | }
62 | if base.DefaultPage == nil {
63 | base.DefaultPage = defaults.DefaultPage
64 | }
65 | if base.AllowInsecureMarkup == nil {
66 | base.AllowInsecureMarkup = defaults.AllowInsecureMarkup
67 | }
68 | if base.Lock == nil {
69 | base.Lock = defaults.Lock
70 | }
71 | if base.DebounceSave == nil {
72 | base.DebounceSave = defaults.DebounceSave
73 | }
74 | if base.Diary == nil {
75 | base.Diary = defaults.Diary
76 | }
77 | if base.AccessCode == nil {
78 | base.AccessCode = defaults.AccessCode
79 | }
80 | if base.FileUploadsAllowed == nil {
81 | base.FileUploadsAllowed = defaults.FileUploadsAllowed
82 | }
83 | if base.MaxFileUploadMb == nil {
84 | base.MaxFileUploadMb = defaults.MaxFileUploadMb
85 | }
86 | if base.MaxDocumentLength == nil {
87 | base.MaxDocumentLength = defaults.MaxDocumentLength
88 | }
89 | if base.TLS == nil {
90 | base.TLS = defaults.TLS
91 | }
92 | if base.CookieKeys == nil {
93 | base.CookieKeys = defaults.CookieKeys
94 | }
95 | }
96 |
97 | func (c *Config) SetDefaults() {
98 | copyDefaults(&c.Default, &DefaultSiteConfig)
99 | for i := range c.Sites {
100 | copyDefaults(&c.Sites[i], &c.Default)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/config/http.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "encoding/base64"
7 | "net/http"
8 | "github.com/gin-contrib/sessions"
9 | "github.com/jcelliott/lumber"
10 | "github.com/schollz/cowyo/server"
11 | "strings"
12 | )
13 | func (c Config) ListenAndServe() error {
14 | insecurePorts := map[int]bool{}
15 | securePorts := map[int]bool{}
16 | err := make(chan error)
17 | for _, s := range c.Sites {
18 | if !insecurePorts[*s.Port] {
19 | insecurePorts[*s.Port] = true
20 | go func(s SiteConfig) {
21 | err <- http.ListenAndServe(fmt.Sprintf("localhost:%d", *s.Port), c)
22 | }(s)
23 | }
24 | if s.TLS != nil && !securePorts[s.TLS.Port] {
25 | securePorts[s.TLS.Port] = true
26 | go func(s SiteConfig) {
27 | err <- http.ListenAndServeTLS(
28 | fmt.Sprintf("localhost:%d", s.TLS.Port),
29 | s.TLS.CertPath,
30 | s.TLS.KeyPath,
31 | c,
32 | )
33 | }(s)
34 | }
35 | }
36 | for {
37 | return <- err
38 | }
39 | return nil
40 | }
41 |
42 | func (c Config) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
43 | for i := range c.Sites {
44 | if c.Sites[i].MatchesRequest(r) {
45 | c.Sites[i].Handle(rw, r)
46 | return
47 | }
48 | }
49 | http.NotFound(rw, r)
50 | }
51 |
52 | func (s SiteConfig) MatchesRequest(r *http.Request) bool {
53 | sh := *s.Host
54 | if strings.HasPrefix(sh, "*") {
55 | return strings.HasSuffix(r.Host, sh[1:])
56 | }
57 | return sh == r.Host
58 | }
59 |
60 | func (s SiteConfig) sessionStore() sessions.Store {
61 | keys := [][]byte{}
62 | for _, k := range s.CookieKeys {
63 | key, err := base64.StdEncoding.DecodeString(k.AuthenticateBase64)
64 | if err != nil {
65 | panic(err)
66 | }
67 | if len(key) != 32 {
68 | log.Panicf("AuthenticateBase64 key %s must be 32 bytes; suggest %s", k.AuthenticateBase64, makeAuthKey())
69 | }
70 |
71 | keys = append(keys, key)
72 | key, err = base64.StdEncoding.DecodeString(k.EncryptBase64)
73 | if err != nil {
74 | panic(err)
75 | }
76 |
77 | if len(key) != 32 {
78 | log.Panicf("EncryptBase64 key %s must be 32 bytes, suggest %s", k.EncryptBase64, makeAuthKey())
79 | }
80 | keys = append(keys, key)
81 | }
82 | return sessions.NewStore(keys...)
83 | }
84 |
85 | func (s SiteConfig) Handle(rw http.ResponseWriter, r *http.Request) {
86 | dataDir := strings.Replace(*s.DataDir, "${HOST}", r.Host, -1)
87 |
88 | router := server.Site{
89 | PathToData: dataDir,
90 | Css: []byte{},
91 | DefaultPage: *s.DefaultPage,
92 | DefaultPassword: *s.Lock,
93 | Debounce: *s.DebounceSave,
94 | Diary: *s.Diary,
95 | SessionStore: s.sessionStore(),
96 | SecretCode: *s.AccessCode,
97 | AllowInsecure: *s.AllowInsecureMarkup,
98 | Fileuploads: *s.MaxFileUploadMb > 0,
99 | MaxUploadSize: *s.MaxFileUploadMb,
100 | Logger: lumber.NewConsoleLogger(server.LogLevel),
101 | MaxDocumentSize: *s.MaxDocumentLength,
102 | }.Router()
103 |
104 | router.ServeHTTP(rw, r)
105 | }
106 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | services:
4 | cowyo:
5 | build: .
6 | ports:
7 | - 8050:8050
8 | volumes:
9 | - ./data:/data
--------------------------------------------------------------------------------
/encrypt/encrypt.go:
--------------------------------------------------------------------------------
1 | package encrypt
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/hex"
6 |
7 | "github.com/schollz/cryptopasta"
8 | )
9 |
10 | func EncryptString(toEncrypt string, password string) (string, error) {
11 | key := sha256.Sum256([]byte(password))
12 | encrypted, err := cryptopasta.Encrypt([]byte(toEncrypt), &key)
13 | if err != nil {
14 | return "", err
15 | }
16 |
17 | return hex.EncodeToString(encrypted), nil
18 | }
19 |
20 | func DecryptString(toDecrypt string, password string) (string, error) {
21 | key := sha256.Sum256([]byte(password))
22 | contentData, err := hex.DecodeString(toDecrypt)
23 | if err != nil {
24 | return "", err
25 | }
26 | bDecrypted, err := cryptopasta.Decrypt(contentData, &key)
27 | return string(bDecrypted), err
28 | }
29 |
--------------------------------------------------------------------------------
/encrypt/encrypt_test.go:
--------------------------------------------------------------------------------
1 | package encrypt
2 |
3 | import "testing"
4 |
5 | func TestEncryption(t *testing.T) {
6 | s, err := EncryptString("some string", "some password")
7 | if err != nil {
8 | t.Errorf("What")
9 | }
10 | d, err := DecryptString(s, "some wrong password")
11 | if err == nil {
12 | t.Errorf("Should throw error for bad password")
13 | }
14 | d, err = DecryptString(s, "some password")
15 | if err != nil {
16 | t.Errorf("Should not throw password")
17 | }
18 | if d != "some string" {
19 | t.Errorf("Problem decoding")
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/schollz/cowyo
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/BurntSushi/toml v1.3.2
7 | github.com/bytedance/sonic v1.9.2 // indirect
8 | github.com/danielheath/gin-teeny-security v0.0.0-20180331042316-bb11804dd0e2
9 | github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271
10 | github.com/gin-contrib/sessions v0.0.5
11 | github.com/gin-gonic/gin v1.9.1
12 | github.com/go-playground/validator/v10 v10.14.1 // indirect
13 | github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25
14 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect
15 | github.com/microcosm-cc/bluemonday v1.0.24
16 | github.com/russross/blackfriday v1.6.0 // indirect
17 | github.com/russross/blackfriday/v2 v2.1.0
18 | github.com/schollz/cryptopasta v0.0.0-20170217152710-dcd61c7d42a1
19 | github.com/schollz/versionedtext v0.0.0-20180523061923-d8ce0957c254
20 | github.com/sergi/go-diff v1.3.1 // indirect
21 | github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629
22 | github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 // indirect
23 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 // indirect
24 | github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect
25 | github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b // indirect
26 | github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8 // indirect
27 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
28 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
29 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
30 | golang.org/x/arch v0.4.0 // indirect
31 | golang.org/x/crypto v0.11.0
32 | golang.org/x/net v0.12.0 // indirect
33 | google.golang.org/protobuf v1.31.0 // indirect
34 | gopkg.in/urfave/cli.v1 v1.20.0
35 | )
36 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
3 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
4 | github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
5 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
6 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
7 | github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
8 | github.com/bos-hieu/mongostore v0.0.2/go.mod h1:8AbbVmDEb0yqJsBrWxZIAZOxIfv/tsP8CDtdHduZHGg=
9 | github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
10 | github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
11 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
12 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
13 | github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
14 | github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
15 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
16 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
17 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
18 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
19 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
20 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
21 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
22 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
23 | github.com/danielheath/gin-teeny-security v0.0.0-20180331042316-bb11804dd0e2 h1:OU8xbewlvG+K/mPYGshgkSDJZugXeV4cvlI8r02a58o=
24 | github.com/danielheath/gin-teeny-security v0.0.0-20180331042316-bb11804dd0e2/go.mod h1:iufTPweOVe3TKbMOYF0OyJ5iM4pdK/D9F9dIQoQC4IE=
25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
26 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
27 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
28 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
29 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
30 | github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271 h1:s+boMV47gwTyff2PL+k6V33edJpp+K5y3QPzZlRhno8=
31 | github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271/go.mod h1:XLLtIXoP9+9zGcEDc7gAGV3AksGPO+vzv4kXHMJSdU0=
32 | github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
33 | github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
34 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
35 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
36 | github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
37 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
38 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
39 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
40 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
41 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
42 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
43 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
44 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
45 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
46 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
47 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
48 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
49 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
50 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
51 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
52 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
53 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
54 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
55 | github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
56 | github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
57 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
58 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
59 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
60 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
61 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
62 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
63 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
64 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
65 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
66 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
67 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
68 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
69 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
70 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
71 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
72 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
73 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
74 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
75 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
76 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
77 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
78 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
79 | github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
80 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
81 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
82 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
83 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
84 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
85 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
86 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
87 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
88 | github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
89 | github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
90 | github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
91 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
92 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
93 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
94 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
95 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
96 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
97 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
98 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
99 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
100 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
101 | github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
102 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
103 | github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
104 | github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
105 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
106 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
107 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
108 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
109 | github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
110 | github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
111 | github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
112 | github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
113 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
114 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
115 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
116 | github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
117 | github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
118 | github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
119 | github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
120 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
121 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
122 | github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
123 | github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
124 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
125 | github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8=
126 | github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw=
127 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
128 | github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
129 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
130 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
131 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
132 | github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
133 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
134 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
135 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
136 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
137 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
138 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
139 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
140 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
141 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
142 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
143 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
144 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
145 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
146 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
147 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
148 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
149 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
150 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
151 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
152 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
153 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
154 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
155 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
156 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
157 | github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
158 | github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
159 | github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
160 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
161 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
162 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
163 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
164 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
165 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
166 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
167 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
168 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
169 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
170 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
171 | github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
172 | github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
173 | github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
174 | github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw=
175 | github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
176 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
177 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
178 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
179 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
180 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
181 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
182 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
183 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
184 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
185 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
186 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
187 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
188 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
189 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
190 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
191 | github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
192 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
193 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
194 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
195 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
196 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
197 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
198 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
199 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
200 | github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
201 | github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
202 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
203 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
204 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
205 | github.com/schollz/cryptopasta v0.0.0-20170217152710-dcd61c7d42a1 h1:CAVM5ALs/TKIa2ri7WMqge+m5wz/ItuiU6CFUPjZTjA=
206 | github.com/schollz/cryptopasta v0.0.0-20170217152710-dcd61c7d42a1/go.mod h1:sM7qObCXSAwGYckYHG4m0hP3PSCBcHmC7/w/kBwcwgM=
207 | github.com/schollz/versionedtext v0.0.0-20180523061923-d8ce0957c254 h1:/EgihFrDLhb/x7NLm8cWB7QTquw5gatR+y/jv2gLWsY=
208 | github.com/schollz/versionedtext v0.0.0-20180523061923-d8ce0957c254/go.mod h1:116sjYSGDGoVSTUCdO34dA1Yg1ZGbN2jk/aYThLfK60=
209 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
210 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
211 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
212 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
213 | github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
214 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
215 | github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 h1:86e54L0i3pH3dAIA8OxBbfLrVyhoGpnNk1iJCigAWYs=
216 | github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
217 | github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 h1:SpaoQDTgpo2YZkvmr2mtgloFFfPTjtLMlZkQtNAPQik=
218 | github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
219 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc=
220 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
221 | github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 h1:KaKXZldeYH73dpQL+Nr38j1r5BgpAYQjYvENOUpIZDQ=
222 | github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
223 | github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b h1:rBIwpb5ggtqf0uZZY5BPs1sL7njUMM7I8qD2jiou70E=
224 | github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
225 | github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8 h1:W5meM/5DP0Igf+pS3Se363Y2DoDv9LUuZgQ24uG9LNY=
226 | github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8/go.mod h1:hWBWTvIJ918VxbNOk2hxQg1/5j1M9yQI1Kp8d9qrOq8=
227 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
228 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
229 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
230 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
231 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
232 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
233 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
234 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
235 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
236 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
237 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
238 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
239 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
240 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
241 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
242 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
243 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
244 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
245 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
246 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
247 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
248 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
249 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
250 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
251 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
252 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
253 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
254 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
255 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
256 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
257 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
258 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
259 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
260 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
261 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
262 | github.com/wader/gormstore/v2 v2.0.0/go.mod h1:3BgNKFxRdVo2E4pq3e/eiim8qRDZzaveaIcIvu2T8r0=
263 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
264 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
265 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
266 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
267 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
268 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
269 | go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
270 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
271 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
272 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
273 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
274 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
275 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
276 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
277 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
278 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
279 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
280 | golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
281 | golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
282 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
283 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
284 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
285 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
286 | golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
287 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
288 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
289 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
290 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
291 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
292 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
293 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
294 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
295 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
296 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
297 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
298 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
299 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
300 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
301 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
302 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
303 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
304 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
305 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
306 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
307 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
308 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
309 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
310 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
311 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
312 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
313 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
314 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
315 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
316 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
317 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
318 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
319 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
320 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
321 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
322 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
323 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
324 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
325 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
326 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
327 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
328 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
329 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
330 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
331 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
332 | golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
333 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
334 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
335 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
336 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
337 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
338 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
339 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
340 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
341 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
342 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
343 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
344 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
345 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
346 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
347 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
348 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
349 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
350 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
351 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
352 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
353 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
354 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
355 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
356 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
357 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
358 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
359 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
360 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
361 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
362 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
363 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
364 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
365 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
366 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
367 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
368 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
369 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
370 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
371 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
372 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
373 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
374 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
375 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
376 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
377 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
378 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
379 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
380 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
381 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
382 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
383 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
384 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
385 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
386 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
387 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
388 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
389 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
390 | gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
391 | gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
392 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
393 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
394 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
395 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
396 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
397 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
398 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
399 | gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
400 | gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
401 | gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
402 | gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
403 | gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
404 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
405 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
406 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "os"
7 | "time"
8 |
9 | "github.com/jcelliott/lumber"
10 | "github.com/schollz/cowyo/server"
11 |
12 | cli "gopkg.in/urfave/cli.v1"
13 | )
14 |
15 | var version string
16 | var pathToData string
17 |
18 | func main() {
19 | app := cli.NewApp()
20 | app.Name = "cowyo"
21 | app.Usage = "a simple wiki"
22 | app.Version = version
23 | app.Compiled = time.Now()
24 | app.Action = func(c *cli.Context) error {
25 | pathToData = c.GlobalString("data")
26 | os.MkdirAll(pathToData, 0755)
27 | host := c.GlobalString("host")
28 | crt_f := c.GlobalString("cert") // crt flag
29 | key_f := c.GlobalString("key") // key flag
30 | if host == "" {
31 | host = GetLocalIP()
32 | }
33 | TLS := false
34 | if crt_f != "" && key_f != "" {
35 | TLS = true
36 | }
37 | if TLS {
38 | fmt.Printf("\nRunning cowyo server (version %s) at https://%s:%s\n\n", version, host, c.GlobalString("port"))
39 | } else {
40 | fmt.Printf("\nRunning cowyo server (version %s) at http://%s:%s\n\n", version, host, c.GlobalString("port"))
41 | }
42 |
43 | server.Serve(
44 | pathToData,
45 | c.GlobalString("host"),
46 | c.GlobalString("port"),
47 | c.GlobalString("cert"),
48 | c.GlobalString("key"),
49 | TLS,
50 | c.GlobalString("css"),
51 | c.GlobalString("default-page"),
52 | c.GlobalString("lock"),
53 | c.GlobalInt("debounce"),
54 | c.GlobalBool("diary"),
55 | c.GlobalString("cookie-secret"),
56 | c.GlobalString("access-code"),
57 | c.GlobalBool("allow-insecure-markup"),
58 | c.GlobalBool("allow-file-uploads"),
59 | c.GlobalUint("max-upload-mb"),
60 | c.GlobalUint("max-document-length"),
61 | logger(c.GlobalBool("debug")),
62 | )
63 | return nil
64 | }
65 | app.Flags = []cli.Flag{
66 | cli.StringFlag{
67 | Name: "data",
68 | Value: "data",
69 | Usage: "data folder to use",
70 | },
71 | cli.StringFlag{
72 | Name: "olddata",
73 | Value: "",
74 | Usage: "data folder for migrating",
75 | },
76 | cli.StringFlag{
77 | Name: "host",
78 | Value: "",
79 | Usage: "host to use",
80 | },
81 | cli.StringFlag{
82 | Name: "port,p",
83 | Value: "8050",
84 | Usage: "port to use",
85 | },
86 | cli.StringFlag{
87 | Name: "cert",
88 | Value: "",
89 | Usage: "absolute path to SSL public sertificate",
90 | },
91 | cli.StringFlag{
92 | Name: "key",
93 | Value: "",
94 | Usage: "absolute path to SSL private key",
95 | },
96 | cli.StringFlag{
97 | Name: "css",
98 | Value: "",
99 | Usage: "use a custom CSS file",
100 | },
101 | cli.StringFlag{
102 | Name: "default-page",
103 | Value: "",
104 | Usage: "show default-page/read instead of editing (default: show random editing)",
105 | },
106 | cli.BoolFlag{
107 | Name: "allow-insecure-markup",
108 | Usage: "Skip HTML sanitization",
109 | },
110 | cli.StringFlag{
111 | Name: "lock",
112 | Value: "",
113 | Usage: "password to lock editing all files (default: all pages unlocked)",
114 | },
115 | cli.IntFlag{
116 | Name: "debounce",
117 | Value: 500,
118 | Usage: "debounce time for saving data, in milliseconds",
119 | },
120 | cli.BoolFlag{
121 | Name: "debug, d",
122 | Usage: "turn on debugging",
123 | },
124 | cli.BoolFlag{
125 | Name: "diary",
126 | Usage: "turn diary mode (doing New will give a timestamped page)",
127 | },
128 | cli.StringFlag{
129 | Name: "access-code",
130 | Value: "",
131 | Usage: "Secret code to login with before accessing any wiki stuff",
132 | },
133 | cli.StringFlag{
134 | Name: "cookie-secret",
135 | Value: "secret",
136 | Usage: "random data to use for cookies; changing it will invalidate all sessions",
137 | },
138 | cli.BoolFlag{
139 | Name: "allow-file-uploads",
140 | Usage: "Enable file uploads",
141 | },
142 | cli.UintFlag{
143 | Name: "max-upload-mb",
144 | Value: 2,
145 | Usage: "Largest file upload (in mb) allowed",
146 | },
147 | cli.UintFlag{
148 | Name: "max-document-length",
149 | Value: 100000000,
150 | Usage: "Largest wiki page (in characters) allowed",
151 | },
152 | }
153 | app.Commands = []cli.Command{
154 | {
155 | Name: "migrate",
156 | Aliases: []string{"m"},
157 | Usage: "migrate from the old cowyo",
158 | Action: func(c *cli.Context) error {
159 | pathToData = c.GlobalString("data")
160 | pathToOldData := c.GlobalString("olddata")
161 | if len(pathToOldData) == 0 {
162 | fmt.Printf("You need to specify folder with -olddata")
163 | return nil
164 | }
165 | os.MkdirAll(pathToData, 0755)
166 | if !exists(pathToOldData) {
167 | fmt.Printf("Can not find '%s', does it exist?", pathToOldData)
168 | return nil
169 | }
170 | server.Migrate(pathToOldData, pathToData, logger(c.GlobalBool("debug")))
171 | return nil
172 | },
173 | },
174 | }
175 |
176 | app.Run(os.Args)
177 | }
178 |
179 | // GetLocalIP returns the local ip address
180 | func GetLocalIP() string {
181 | addrs, err := net.InterfaceAddrs()
182 | if err != nil {
183 | return ""
184 | }
185 | bestIP := ""
186 | for _, address := range addrs {
187 | // check the address type and if it is not a loopback the display it
188 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
189 | if ipnet.IP.To4() != nil {
190 | return ipnet.IP.String()
191 | }
192 | }
193 | }
194 | return bestIP
195 | }
196 |
197 | // exists returns whether the given file or directory exists or not
198 | func exists(path string) bool {
199 | _, err := os.Stat(path)
200 | return !os.IsNotExist(err)
201 | }
202 |
203 | func logger(debug bool) *lumber.ConsoleLogger {
204 | if !debug {
205 | return lumber.NewConsoleLogger(lumber.WARN)
206 | }
207 | return lumber.NewConsoleLogger(lumber.TRACE)
208 |
209 | }
210 |
--------------------------------------------------------------------------------
/multisite_sample.toml:
--------------------------------------------------------------------------------
1 | [default]
2 | dataDir = "root_data/${HOST}"
3 | maxDocumentLength = 100000000
4 |
5 | # Specify multiple times to change keys without expiring sessions
6 | [[default.CookieKeys]]
7 | authenticateBase64 = "RpW4LjGCPNOx75G8DrywmzlEHLB/ISXCAAayZ47Ifkc="
8 | encryptBase64 = "ofCKkrfosQb5T4cvz7R5IMP4BQUDHOPsLSMZZy2CUOA="
9 |
10 | [[sites]]
11 | host = "nerdy.party"
12 | dataDir = "somewhere else"
13 | # theme = "custom.css" # TODO: Theme support. Would prefer to move to a complete directory replacement.
14 | defaultPage = "welcome"
15 | allowInsecureMarkup = true
16 | lock = "1234"
17 | debounceSave = 600
18 | diary = true
19 | accessCode = "correct horse battery staple"
20 | fileUploadsAllowed = true
21 | maxFileUploadMb = 6
22 | port = 8090
23 |
24 | #[sites.TLS]
25 | # TODO: ACME support eg letsencrypt
26 | #certPath = "path.crt"
27 | #keyPath = "path.key"
28 | #port = 8443
29 |
30 | [[sites]]
31 | host = "cowyo.com"
32 | allowInsecureMarkup = false
33 | fileUploadsAllowed = false
34 | port = 8090
35 |
36 | # Catchall config
37 | [[sites]]
38 | host = "*"
39 | port = 8100
40 | cookieSecret = "ASADFGKLJSH+4t4cC2X3f7GzsLZ+wtST67qoLuErpugJz06ZIpdDHEjcMxR+XOLM"
41 |
--------------------------------------------------------------------------------
/server/debug.go:
--------------------------------------------------------------------------------
1 | // +build debug
2 |
3 | package server
4 |
5 | import "github.com/jcelliott/lumber"
6 |
7 | func init() {
8 | hotTemplateReloading = true
9 | LogLevel = lumber.TRACE
10 | }
11 |
--------------------------------------------------------------------------------
/server/handlers.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "crypto/sha256"
5 | "fmt"
6 | "html/template"
7 | "io"
8 | "io/ioutil"
9 | "net/http"
10 | "net/url"
11 | "os"
12 | "path"
13 | "strconv"
14 | "strings"
15 | "sync"
16 | "time"
17 |
18 | secretRequired "github.com/danielheath/gin-teeny-security"
19 | "github.com/gin-contrib/multitemplate"
20 | "github.com/gin-contrib/sessions"
21 | "github.com/gin-contrib/sessions/cookie"
22 | "github.com/gin-gonic/gin"
23 | "github.com/jcelliott/lumber"
24 | "github.com/schollz/cowyo/encrypt"
25 | )
26 |
27 | const minutesToUnlock = 10.0
28 |
29 | type Site struct {
30 | PathToData string
31 | Css []byte
32 | DefaultPage string
33 | DefaultPassword string
34 | Debounce int
35 | Diary bool
36 | SessionStore cookie.Store
37 | SecretCode string
38 | AllowInsecure bool
39 | Fileuploads bool
40 | MaxUploadSize uint
41 | Logger *lumber.ConsoleLogger
42 | MaxDocumentSize uint // in runes; about a 10mb limit by default
43 | saveMut sync.Mutex
44 | sitemapUpToDate bool // TODO this makes everything use a pointer
45 | }
46 |
47 | func (s *Site) defaultLock() string {
48 | if s.DefaultPassword == "" {
49 | return ""
50 | }
51 | return HashPassword(s.DefaultPassword)
52 | }
53 |
54 | var hotTemplateReloading bool
55 | var LogLevel int = lumber.WARN
56 |
57 | func Serve(
58 | filepathToData,
59 | host,
60 | port,
61 | crt_path,
62 | key_path string,
63 | TLS bool,
64 | cssFile string,
65 | defaultPage string,
66 | defaultPassword string,
67 | debounce int,
68 | diary bool,
69 | secret string,
70 | secretCode string,
71 | allowInsecure bool,
72 | fileuploads bool,
73 | maxUploadSize uint,
74 | maxDocumentSize uint,
75 | logger *lumber.ConsoleLogger,
76 | ) {
77 | var customCSS []byte
78 | // collect custom CSS
79 | if len(cssFile) > 0 {
80 | var errRead error
81 | customCSS, errRead = ioutil.ReadFile(cssFile)
82 | if errRead != nil {
83 | fmt.Println(errRead)
84 | return
85 | }
86 | fmt.Printf("Loaded CSS file, %d bytes\n", len(customCSS))
87 | }
88 |
89 | router := Site{
90 | PathToData: filepathToData,
91 | Css: customCSS,
92 | DefaultPage: defaultPage,
93 | DefaultPassword: defaultPassword,
94 | Debounce: debounce,
95 | Diary: diary,
96 | SessionStore: cookie.NewStore([]byte(secret)),
97 | SecretCode: secretCode,
98 | AllowInsecure: allowInsecure,
99 | Fileuploads: fileuploads,
100 | MaxUploadSize: maxUploadSize,
101 | Logger: logger,
102 | MaxDocumentSize: maxDocumentSize,
103 | }.Router()
104 |
105 | if TLS {
106 | http.ListenAndServeTLS(host+":"+port, crt_path, key_path, router)
107 | } else {
108 | panic(router.Run(host + ":" + port))
109 | }
110 | }
111 |
112 | func (s Site) Router() *gin.Engine {
113 | if s.Logger == nil {
114 | s.Logger = lumber.NewConsoleLogger(lumber.TRACE)
115 | }
116 |
117 | if hotTemplateReloading {
118 | gin.SetMode(gin.DebugMode)
119 | } else {
120 | gin.SetMode(gin.ReleaseMode)
121 | }
122 |
123 | router := gin.Default()
124 | router.SetFuncMap(template.FuncMap{
125 | "sniffContentType": s.sniffContentType,
126 | })
127 |
128 | if hotTemplateReloading {
129 | router.LoadHTMLGlob("templates/*.tmpl")
130 | } else {
131 | router.HTMLRender = s.loadTemplates("index.tmpl")
132 | }
133 |
134 | router.Use(sessions.Sessions("_session", s.SessionStore))
135 | if s.SecretCode != "" {
136 | cfg := &secretRequired.Config{
137 | Secret: s.SecretCode,
138 | Path: "/login/",
139 | RequireAuth: func(c *gin.Context) bool {
140 | page := c.Param("page")
141 | cmd := c.Param("command")
142 |
143 | if page == "sitemap.xml" || page == "favicon.ico" || page == "static" || page == "uploads" {
144 | return false // no auth for these
145 | }
146 |
147 | if page != "" && cmd == "/read" {
148 | p := s.Open(page)
149 | if p != nil && p.IsPublished {
150 | return false // Published pages don't require auth.
151 | }
152 | }
153 | return true
154 | },
155 | }
156 | router.Use(cfg.Middleware)
157 | }
158 |
159 | // router.Use(static.Serve("/static/", static.LocalFile("./static", true)))
160 | router.GET("/", func(c *gin.Context) {
161 | if s.DefaultPage != "" {
162 | c.Redirect(302, "/"+s.DefaultPage+"/read")
163 | } else {
164 | c.Redirect(302, "/"+randomAlliterateCombo())
165 | }
166 | })
167 |
168 | router.POST("/uploads", s.handleUpload)
169 |
170 | router.GET("/:page", func(c *gin.Context) {
171 | page := c.Param("page")
172 | c.Redirect(302, "/"+page+"/")
173 | })
174 | router.GET("/:page/*command", s.handlePageRequest)
175 | router.POST("/update", s.handlePageUpdate)
176 | router.POST("/relinquish", s.handlePageRelinquish) // relinquish returns the page no matter what (and destroys if nessecary)
177 | router.POST("/exists", s.handlePageExists)
178 | router.POST("/prime", s.handlePrime)
179 | router.POST("/lock", s.handleLock)
180 | router.POST("/publish", s.handlePublish)
181 | router.POST("/encrypt", s.handleEncrypt)
182 | router.DELETE("/oldlist", s.handleClearOldListItems)
183 | router.DELETE("/listitem", s.deleteListItem)
184 |
185 | // start long-processes as threads
186 | go s.thread_SiteMap()
187 |
188 | // Allow iframe/scripts in markup?
189 | allowInsecureHtml = s.AllowInsecure
190 | return router
191 | }
192 |
193 | func (s *Site) loadTemplates(list ...string) multitemplate.Render {
194 | r := multitemplate.New()
195 |
196 | for _, x := range list {
197 | templateString, err := Asset("templates/" + x)
198 | if err != nil {
199 | panic(err)
200 | }
201 |
202 | tmplMessage, err := template.New(x).Funcs(template.FuncMap{
203 | "sniffContentType": s.sniffContentType,
204 | }).Parse(string(templateString))
205 | if err != nil {
206 | panic(err)
207 | }
208 |
209 | r.Add(x, tmplMessage)
210 | }
211 |
212 | return r
213 | }
214 |
215 | func pageIsLocked(p *Page, c *gin.Context) bool {
216 | // it is easier to reason about when the page is actually unlocked
217 | var unlocked = !p.IsLocked ||
218 | (p.IsLocked && p.UnlockedFor == getSetSessionID(c))
219 | return !unlocked
220 | }
221 |
222 | func (s *Site) handlePageRelinquish(c *gin.Context) {
223 | type QueryJSON struct {
224 | Page string `json:"page"`
225 | }
226 | var json QueryJSON
227 | err := c.BindJSON(&json)
228 | if err != nil {
229 | s.Logger.Trace(err.Error())
230 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
231 | return
232 | }
233 | if len(json.Page) == 0 {
234 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"})
235 | return
236 | }
237 | message := "Relinquished"
238 | p := s.Open(json.Page)
239 | name := p.Meta
240 | if name == "" {
241 | name = json.Page
242 | }
243 | text := p.Text.GetCurrent()
244 | isLocked := pageIsLocked(p, c)
245 | isEncrypted := p.IsEncrypted
246 | destroyed := p.IsPrimedForSelfDestruct
247 | if !isLocked && p.IsPrimedForSelfDestruct {
248 | p.Erase()
249 | message = "Relinquished and erased"
250 | }
251 | c.JSON(http.StatusOK, gin.H{"success": true,
252 | "name": name,
253 | "message": message,
254 | "text": text,
255 | "locked": isLocked,
256 | "encrypted": isEncrypted,
257 | "destroyed": destroyed})
258 | }
259 |
260 | func getSetSessionID(c *gin.Context) (sid string) {
261 | var (
262 | session = sessions.Default(c)
263 | v = session.Get("sid")
264 | )
265 | if v != nil {
266 | sid = v.(string)
267 | }
268 | if v == nil || sid == "" {
269 | sid = RandStringBytesMaskImprSrc(8)
270 | session.Set("sid", sid)
271 | session.Save()
272 | }
273 | return sid
274 | }
275 |
276 | func (s *Site) thread_SiteMap() {
277 | for {
278 | if !s.sitemapUpToDate {
279 | s.Logger.Info("Generating sitemap...")
280 | s.sitemapUpToDate = true
281 | ioutil.WriteFile(path.Join(s.PathToData, "sitemap.xml"), []byte(s.generateSiteMap()), 0644)
282 | s.Logger.Info("..finished generating sitemap")
283 | }
284 | time.Sleep(time.Second)
285 | }
286 | }
287 |
288 | func (s *Site) generateSiteMap() (sitemap string) {
289 | files, _ := ioutil.ReadDir(s.PathToData)
290 | lastEdited := make([]string, len(files))
291 | names := make([]string, len(files))
292 | i := 0
293 | for _, f := range files {
294 | names[i] = DecodeFileName(f.Name())
295 | p := s.Open(names[i])
296 | if p.IsPublished {
297 | lastEdited[i] = time.Unix(p.Text.LastEditTime()/1000000000, 0).Format("2006-01-02")
298 | i++
299 | }
300 | }
301 | names = names[:i]
302 | lastEdited = lastEdited[:i]
303 | sitemap = ""
304 | for i := range names {
305 | sitemap += fmt.Sprintf(`
306 |
307 | {{ .Request.Host }}/%s/read
308 | %s
309 | monthly
310 | 0.8
311 |
312 | `, names[i], lastEdited[i])
313 | }
314 | sitemap += ""
315 | return
316 | }
317 |
318 | func (s *Site) handlePageRequest(c *gin.Context) {
319 | page := c.Param("page")
320 | command := c.Param("command")
321 |
322 | if page == "sitemap.xml" {
323 | siteMap, err := ioutil.ReadFile(path.Join(s.PathToData, "sitemap.xml"))
324 | if err != nil {
325 | c.Data(http.StatusInternalServerError, contentType("sitemap.xml"), []byte(""))
326 | } else {
327 | fmt.Fprintln(c.Writer, ``)
328 | template.Must(template.New("sitemap").Parse(string(siteMap))).Execute(c.Writer, c)
329 | }
330 | return
331 | } else if page == "favicon.ico" {
332 | data, _ := Asset("/static/img/cowyo/favicon.ico")
333 | c.Data(http.StatusOK, contentType("/static/img/cowyo/favicon.ico"), data)
334 | return
335 | } else if page == "static" {
336 | filename := page + command
337 | var data []byte
338 | if filename == "static/css/custom.css" {
339 | data = s.Css
340 | } else {
341 | var errAssset error
342 | data, errAssset = Asset(filename)
343 | if errAssset != nil {
344 | c.String(http.StatusInternalServerError, "Could not find data")
345 | }
346 | }
347 | c.Data(http.StatusOK, contentType(filename), data)
348 | return
349 | } else if page == "uploads" {
350 | if len(command) == 0 || command == "/" || command == "/edit" {
351 | if !s.Fileuploads {
352 | c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
353 | return
354 | }
355 | } else {
356 | command = command[1:]
357 | if !strings.HasSuffix(command, ".upload") {
358 | command = command + ".upload"
359 | }
360 | pathname := path.Join(s.PathToData, command)
361 |
362 | if allowInsecureHtml {
363 | c.Header(
364 | "Content-Disposition",
365 | `inline; filename="`+c.DefaultQuery("filename", "upload")+`"`,
366 | )
367 | } else {
368 | // Prevent malicious html uploads by forcing type to plaintext and 'download-instead-of-view'
369 | c.Header("Content-Type", "text/plain")
370 | c.Header(
371 | "Content-Disposition",
372 | `attachment; filename="`+c.DefaultQuery("filename", "upload")+`"`,
373 | )
374 | }
375 | c.File(pathname)
376 | return
377 | }
378 | }
379 |
380 | p := s.Open(page)
381 | if len(command) < 2 {
382 | if p.IsPublished {
383 | c.Redirect(302, "/"+page+"/read")
384 | } else {
385 | c.Redirect(302, "/"+page+"/edit")
386 | }
387 | return
388 | }
389 |
390 | // use the default lock
391 | if s.defaultLock() != "" && p.IsNew() {
392 | p.IsLocked = true
393 | p.PassphraseToUnlock = s.defaultLock()
394 | }
395 |
396 | version := c.DefaultQuery("version", "ajksldfjl")
397 | isLocked := pageIsLocked(p, c)
398 |
399 | // Disallow anything but viewing locked/encrypted pages
400 | if (p.IsEncrypted || isLocked) &&
401 | (command[0:2] != "/v" && command[0:2] != "/r") {
402 | c.Redirect(302, "/"+page+"/view")
403 | return
404 | }
405 |
406 | // Destroy page if it is opened and primed
407 | if p.IsPrimedForSelfDestruct && !isLocked && !p.IsEncrypted {
408 | p.Update("This page has self-destructed. You cannot return to it.\n\n" + p.Text.GetCurrent())
409 | p.Erase()
410 | if p.IsPublished {
411 | command = "/read"
412 | } else {
413 | command = "/view"
414 | }
415 | }
416 | if command == "/erase" {
417 | if !isLocked && !p.IsEncrypted {
418 | p.Erase()
419 | c.Redirect(302, "/"+page+"/edit")
420 | } else {
421 | c.Redirect(302, "/"+page+"/view")
422 | }
423 | return
424 | }
425 | rawText := p.Text.GetCurrent()
426 | rawHTML := p.RenderedPage
427 |
428 | // Check to see if an old version is requested
429 | versionInt, versionErr := strconv.Atoi(version)
430 | if versionErr == nil && versionInt > 0 {
431 | versionText, err := p.Text.GetPreviousByTimestamp(int64(versionInt))
432 | if err == nil {
433 | rawText = versionText
434 | rawHTML = GithubMarkdownToHTML(rawText)
435 | }
436 | }
437 |
438 | // Get history
439 | var versionsInt64 []int64
440 | var versionsChangeSums []int
441 | var versionsText []string
442 | if command[0:2] == "/h" {
443 | versionsInt64, versionsChangeSums = p.Text.GetMajorSnapshotsAndChangeSums(60) // get snapshots 60 seconds apart
444 | versionsText = make([]string, len(versionsInt64))
445 | for i, v := range versionsInt64 {
446 | versionsText[i] = time.Unix(v/1000000000, 0).Format("Mon Jan 2 15:04:05 MST 2006")
447 | }
448 | versionsText = reverseSliceString(versionsText)
449 | versionsInt64 = reverseSliceInt64(versionsInt64)
450 | versionsChangeSums = reverseSliceInt(versionsChangeSums)
451 | }
452 |
453 | if len(command) > 3 && command[0:3] == "/ra" {
454 | c.Writer.Header().Set("Content-Type", "text/plain")
455 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
456 | c.Writer.Header().Set("Access-Control-Max-Age", "86400")
457 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
458 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
459 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
460 | c.Data(200, contentType(p.Name), []byte(rawText))
461 | return
462 | }
463 |
464 | var DirectoryEntries []os.FileInfo
465 | if page == "ls" {
466 | command = "/view"
467 | DirectoryEntries = s.DirectoryList()
468 | }
469 | if page == "uploads" {
470 | command = "/view"
471 | var err error
472 | DirectoryEntries, err = s.UploadList()
473 | if err != nil {
474 | c.AbortWithError(http.StatusInternalServerError, err)
475 | return
476 | }
477 | }
478 |
479 | // swap out /view for /read if it is published
480 | if p.IsPublished {
481 | rawHTML = strings.Replace(rawHTML, "/view", "/read", -1)
482 | }
483 |
484 | c.HTML(http.StatusOK, "index.tmpl", gin.H{
485 | "EditPage": command[0:2] == "/e", // /edit
486 | "ViewPage": command[0:2] == "/v", // /view
487 | "ListPage": command[0:2] == "/l", // /list
488 | "HistoryPage": command[0:2] == "/h", // /history
489 | "ReadPage": command[0:2] == "/r", // /history
490 | "DontKnowPage": command[0:2] != "/e" &&
491 | command[0:2] != "/v" &&
492 | command[0:2] != "/l" &&
493 | command[0:2] != "/r" &&
494 | command[0:2] != "/h",
495 | "DirectoryPage": page == "ls" || page == "uploads",
496 | "UploadPage": page == "uploads",
497 | "DirectoryEntries": DirectoryEntries,
498 | "Page": page,
499 | "RenderedPage": template.HTML([]byte(rawHTML)),
500 | "RawPage": rawText,
501 | "Versions": versionsInt64,
502 | "VersionsText": versionsText,
503 | "VersionsChangeSums": versionsChangeSums,
504 | "IsLocked": isLocked,
505 | "IsEncrypted": p.IsEncrypted,
506 | "ListItems": renderList(rawText),
507 | "Route": "/" + page + command,
508 | "HasDotInName": strings.Contains(page, "."),
509 | "RecentlyEdited": getRecentlyEdited(page, c),
510 | "IsPublished": p.IsPublished,
511 | "CustomCSS": len(s.Css) > 0,
512 | "Debounce": s.Debounce,
513 | "DiaryMode": s.Diary,
514 | "Date": time.Now().Format("2006-01-02"),
515 | "UnixTime": time.Now().Unix(),
516 | "ChildPageNames": p.ChildPageNames(),
517 | "AllowFileUploads": s.Fileuploads,
518 | "MaxUploadMB": s.MaxUploadSize,
519 | })
520 | }
521 |
522 | func getRecentlyEdited(title string, c *gin.Context) []string {
523 | session := sessions.Default(c)
524 | var recentlyEdited string
525 | v := session.Get("recentlyEdited")
526 | editedThings := []string{}
527 | if v == nil {
528 | recentlyEdited = title
529 | } else {
530 | editedThings = strings.Split(v.(string), "|||")
531 | if !stringInSlice(title, editedThings) {
532 | recentlyEdited = v.(string) + "|||" + title
533 | } else {
534 | recentlyEdited = v.(string)
535 | }
536 | }
537 | session.Set("recentlyEdited", recentlyEdited)
538 | session.Save()
539 | editedThingsWithoutCurrent := make([]string, len(editedThings))
540 | i := 0
541 | for _, thing := range editedThings {
542 | if thing == title {
543 | continue
544 | }
545 | if strings.Contains(thing, "icon-") {
546 | continue
547 | }
548 | editedThingsWithoutCurrent[i] = thing
549 | i++
550 | }
551 | return editedThingsWithoutCurrent[:i]
552 | }
553 |
554 | func (s *Site) handlePageExists(c *gin.Context) {
555 | type QueryJSON struct {
556 | Page string `json:"page"`
557 | }
558 | var json QueryJSON
559 | err := c.BindJSON(&json)
560 | if err != nil {
561 | s.Logger.Trace(err.Error())
562 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON", "exists": false})
563 | return
564 | }
565 | p := s.Open(json.Page)
566 | if len(p.Text.GetCurrent()) > 0 {
567 | c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " found", "exists": true})
568 | } else {
569 | c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " not found", "exists": false})
570 | }
571 |
572 | }
573 |
574 | func (s *Site) handlePageUpdate(c *gin.Context) {
575 | type QueryJSON struct {
576 | Page string `json:"page"`
577 | NewText string `json:"new_text"`
578 | FetchedAt int64 `json:"fetched_at"`
579 | IsEncrypted bool `json:"is_encrypted"`
580 | IsPrimed bool `json:"is_primed"`
581 | Meta string `json:"meta"`
582 | }
583 | var json QueryJSON
584 | err := c.BindJSON(&json)
585 | if err != nil {
586 | s.Logger.Trace(err.Error())
587 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
588 | return
589 | }
590 | if uint(len(json.NewText)) > s.MaxDocumentSize {
591 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Too much"})
592 | return
593 | }
594 | if len(json.Page) == 0 {
595 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"})
596 | return
597 | }
598 | s.Logger.Trace("Update: %v", json)
599 | p := s.Open(json.Page)
600 | var (
601 | message string
602 | sinceLastEdit = time.Since(p.LastEditTime())
603 | )
604 | success := false
605 | if pageIsLocked(p, c) {
606 | if sinceLastEdit < minutesToUnlock {
607 | message = "This page is being edited by someone else"
608 | } else {
609 | // here what might have happened is that two people unlock without
610 | // editing thus they both suceeds but only one is able to edit
611 | message = "Locked, must unlock first"
612 | }
613 | } else if p.IsEncrypted {
614 | message = "Encrypted, must decrypt first"
615 | } else if json.FetchedAt > 0 && p.LastEditUnixTime() > json.FetchedAt {
616 | message = "Refusing to overwrite others work"
617 | } else {
618 | p.Meta = json.Meta
619 | p.Update(json.NewText)
620 | if json.IsEncrypted {
621 | p.IsEncrypted = true
622 | }
623 | if json.IsPrimed {
624 | p.IsPrimedForSelfDestruct = true
625 | }
626 | p.Save()
627 | message = "Saved"
628 | if p.IsPublished {
629 | s.sitemapUpToDate = false
630 | }
631 | success = true
632 | }
633 | c.JSON(http.StatusOK, gin.H{"success": success, "message": message, "unix_time": time.Now().Unix()})
634 | }
635 |
636 | func (s *Site) handlePrime(c *gin.Context) {
637 | type QueryJSON struct {
638 | Page string `json:"page"`
639 | }
640 | var json QueryJSON
641 | if c.BindJSON(&json) != nil {
642 | c.String(http.StatusBadRequest, "Problem binding keys")
643 | return
644 | }
645 | s.Logger.Trace("Update: %v", json)
646 | p := s.Open(json.Page)
647 | if pageIsLocked(p, c) {
648 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
649 | return
650 | } else if p.IsEncrypted {
651 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
652 | return
653 | }
654 | p.IsPrimedForSelfDestruct = true
655 | p.Save()
656 | c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"})
657 | }
658 |
659 | func (s *Site) handleLock(c *gin.Context) {
660 | type QueryJSON struct {
661 | Page string `json:"page"`
662 | Passphrase string `json:"passphrase"`
663 | }
664 |
665 | var json QueryJSON
666 | if c.BindJSON(&json) != nil {
667 | c.String(http.StatusBadRequest, "Problem binding keys")
668 | return
669 | }
670 | p := s.Open(json.Page)
671 | if s.defaultLock() != "" && p.IsNew() {
672 | p.IsLocked = true // IsLocked was replaced by variable wrt Context
673 | p.PassphraseToUnlock = s.defaultLock()
674 | }
675 |
676 | if p.IsEncrypted {
677 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
678 | return
679 | }
680 | var (
681 | message string
682 | sessionID = getSetSessionID(c)
683 | sinceLastEdit = time.Since(p.LastEditTime())
684 | )
685 |
686 | // both lock/unlock ends here on locked&timeout combination
687 | if p.IsLocked &&
688 | p.UnlockedFor != sessionID &&
689 | p.UnlockedFor != "" &&
690 | sinceLastEdit.Minutes() < minutesToUnlock {
691 |
692 | c.JSON(http.StatusOK, gin.H{
693 | "success": false,
694 | "message": fmt.Sprintf("This page is being edited by someone else! Will unlock automatically %2.0f minutes after the last change.", minutesToUnlock-sinceLastEdit.Minutes()),
695 | })
696 | return
697 | }
698 | if !pageIsLocked(p, c) {
699 | p.IsLocked = true
700 | p.PassphraseToUnlock = HashPassword(json.Passphrase)
701 | p.UnlockedFor = ""
702 | message = "Locked"
703 | } else {
704 | err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
705 | if err2 != nil {
706 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
707 | return
708 | }
709 | p.UnlockedFor = sessionID
710 | message = "Unlocked only for you"
711 | }
712 | p.Save()
713 | c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
714 | }
715 |
716 | func (s *Site) handlePublish(c *gin.Context) {
717 | type QueryJSON struct {
718 | Page string `json:"page"`
719 | Publish bool `json:"publish"`
720 | }
721 |
722 | var json QueryJSON
723 | if c.BindJSON(&json) != nil {
724 | c.String(http.StatusBadRequest, "Problem binding keys")
725 | return
726 | }
727 | p := s.Open(json.Page)
728 | p.IsPublished = json.Publish
729 | p.Save()
730 | message := "Published"
731 | if !p.IsPublished {
732 | message = "Unpublished"
733 | }
734 | s.sitemapUpToDate = false
735 | c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
736 | }
737 |
738 | func (s *Site) handleUpload(c *gin.Context) {
739 | if !s.Fileuploads {
740 | c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
741 | return
742 | }
743 |
744 | file, info, err := c.Request.FormFile("file")
745 | defer file.Close()
746 | if err != nil {
747 | c.AbortWithError(http.StatusInternalServerError, err)
748 | return
749 | }
750 |
751 | h := sha256.New()
752 | if _, err := io.Copy(h, file); err != nil {
753 | c.AbortWithError(http.StatusInternalServerError, err)
754 | return
755 | }
756 |
757 | newName := "sha256-" + encodeBytesToBase32(h.Sum(nil))
758 |
759 | // Replaces any existing version, but sha256 collisions are rare as anything.
760 | outfile, err := os.Create(path.Join(s.PathToData, newName+".upload"))
761 | if err != nil {
762 | c.AbortWithError(http.StatusInternalServerError, err)
763 | return
764 | }
765 |
766 | file.Seek(0, io.SeekStart)
767 | _, err = io.Copy(outfile, file)
768 | if err != nil {
769 | c.AbortWithError(http.StatusInternalServerError, err)
770 | return
771 | }
772 |
773 | c.Header("Location", "/uploads/"+newName+"?filename="+url.QueryEscape(info.Filename))
774 | return
775 | }
776 |
777 | func (s *Site) handleEncrypt(c *gin.Context) {
778 | type QueryJSON struct {
779 | Page string `json:"page"`
780 | Passphrase string `json:"passphrase"`
781 | }
782 |
783 | var json QueryJSON
784 | if c.BindJSON(&json) != nil {
785 | c.String(http.StatusBadRequest, "Problem binding keys")
786 | return
787 | }
788 | p := s.Open(json.Page)
789 | if pageIsLocked(p, c) {
790 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
791 | return
792 | }
793 | q := s.Open(json.Page)
794 | var message string
795 | if p.IsEncrypted {
796 | decrypted, err2 := encrypt.DecryptString(p.Text.GetCurrent(), json.Passphrase)
797 | if err2 != nil {
798 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong password"})
799 | return
800 | }
801 | q.Erase()
802 | q = s.Open(json.Page)
803 | q.Update(decrypted)
804 | q.IsEncrypted = false
805 | q.IsLocked = p.IsLocked
806 | q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
807 | message = "Decrypted"
808 | } else {
809 | currentText := p.Text.GetCurrent()
810 | encrypted, _ := encrypt.EncryptString(currentText, json.Passphrase)
811 | q.Erase()
812 | q = s.Open(json.Page)
813 | q.Update(encrypted)
814 | q.IsEncrypted = true
815 | q.IsLocked = p.IsLocked
816 | q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
817 | message = "Encrypted"
818 | }
819 | q.Save()
820 | c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
821 | }
822 |
823 | func (s *Site) deleteListItem(c *gin.Context) {
824 | lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
825 | page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname")
826 | if err == nil {
827 | p := s.Open(page)
828 |
829 | _, listItems := reorderList(p.Text.GetCurrent())
830 | newText := p.Text.GetCurrent()
831 | for i, lineString := range listItems {
832 | // fmt.Println(i, lineString, lineNum)
833 | if i+1 == lineNum {
834 | // fmt.Println("MATCHED")
835 | if strings.Contains(lineString, "~~") == false {
836 | // fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
837 | newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
838 | } else {
839 | newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
840 | }
841 | p.Update(newText)
842 | break
843 | }
844 | }
845 |
846 | c.JSON(200, gin.H{
847 | "success": true,
848 | "message": "Done.",
849 | })
850 | } else {
851 | c.JSON(200, gin.H{
852 | "success": false,
853 | "message": err.Error(),
854 | })
855 | }
856 | }
857 |
858 | func (s *Site) handleClearOldListItems(c *gin.Context) {
859 | type QueryJSON struct {
860 | Page string `json:"page"`
861 | }
862 |
863 | var json QueryJSON
864 | if c.BindJSON(&json) != nil {
865 | c.String(http.StatusBadRequest, "Problem binding keys")
866 | return
867 | }
868 | p := s.Open(json.Page)
869 | if p.IsEncrypted {
870 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
871 | return
872 | }
873 | if pageIsLocked(p, c) {
874 | c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
875 | return
876 | }
877 | lines := strings.Split(p.Text.GetCurrent(), "\n")
878 | newLines := make([]string, len(lines))
879 | newLinesI := 0
880 | for _, line := range lines {
881 | if strings.Count(line, "~~") != 2 {
882 | newLines[newLinesI] = line
883 | newLinesI++
884 | }
885 | }
886 | p.Update(strings.Join(newLines[0:newLinesI], "\n"))
887 | p.Save()
888 | c.JSON(http.StatusOK, gin.H{"success": true, "message": "Cleared"})
889 | }
890 |
--------------------------------------------------------------------------------
/server/listify.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "html/template"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | func reorderList(text string) ([]template.HTML, []string) {
10 | listItemsString := ""
11 | for _, lineString := range strings.Split(text, "\n") {
12 | if len(lineString) > 1 {
13 | if string(lineString[0]) != "-" {
14 | listItemsString += "- " + lineString + "\n"
15 | } else {
16 | listItemsString += lineString + "\n"
17 | }
18 | }
19 | }
20 |
21 | // get ordering of template.HTML for rendering
22 | renderedListString := MarkdownToHtml(listItemsString)
23 | listItems := []template.HTML{}
24 | endItems := []template.HTML{}
25 | for _, lineString := range strings.Split(renderedListString, "\n") {
26 | if len(lineString) > 1 {
27 | if strings.Contains(lineString, "") || strings.Contains(lineString, "") {
28 | endItems = append(endItems, template.HTML(lineString))
29 | } else {
30 | listItems = append(listItems, template.HTML(lineString))
31 | }
32 | }
33 | }
34 |
35 | // get ordering of strings for deleting
36 | listItemsStringArray := []string{}
37 | endItemsStringArray := []string{}
38 | for _, lineString := range strings.Split(listItemsString, "\n") {
39 | if len(lineString) > 1 {
40 | if strings.Contains(lineString, "~~") {
41 | endItemsStringArray = append(endItemsStringArray, lineString)
42 | } else {
43 | listItemsStringArray = append(listItemsStringArray, lineString)
44 | }
45 | }
46 | }
47 | return append(listItems, endItems...), append(listItemsStringArray, endItemsStringArray...)
48 | }
49 |
50 | func renderList(currentRawText string) []template.HTML {
51 | listItems, _ := reorderList(currentRawText)
52 | for i := range listItems {
53 | newHTML := strings.Replace(string(listItems[i]), "", ""+``, -1)
54 | newHTML = strings.Replace(newHTML, "", ""+``, -1)
56 | newHTML = strings.Replace(newHTML, "", "", -1)
57 | newHTML = strings.Replace(newHTML, ""+``, ""+``, -1)
58 | newHTML = strings.Replace(newHTML, "", "", -1)
59 | listItems[i] = template.HTML([]byte(newHTML))
60 | }
61 | return listItems
62 | }
63 |
--------------------------------------------------------------------------------
/server/migrate.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "path"
7 |
8 | "github.com/jcelliott/lumber"
9 | )
10 |
11 | func Migrate(pathToOldData, pathToData string, logger *lumber.ConsoleLogger) error {
12 | files, err := ioutil.ReadDir(pathToOldData)
13 | if len(files) == 0 {
14 | return err
15 | }
16 | s := Site{PathToData: pathToData, Logger: lumber.NewConsoleLogger(lumber.TRACE)}
17 | for _, f := range files {
18 | if f.Mode().IsDir() {
19 | continue
20 | }
21 | fmt.Printf("Migrating %s", f.Name())
22 | p := s.Open(f.Name())
23 | bData, err := ioutil.ReadFile(path.Join(pathToOldData, f.Name()))
24 | if err != nil {
25 | return err
26 | }
27 | err = p.Update(string(bData))
28 | if err != nil {
29 | return err
30 | }
31 | if err = p.Save(); err != nil {
32 | return err
33 | }
34 | }
35 | return nil
36 | }
37 |
--------------------------------------------------------------------------------
/server/page.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "os"
7 | "path"
8 | "path/filepath"
9 | "regexp"
10 | "sort"
11 | "strings"
12 | "time"
13 |
14 | "github.com/schollz/versionedtext"
15 | )
16 |
17 | // Page is the basic struct
18 | type Page struct {
19 | Site *Site
20 |
21 | Name string
22 | Text versionedtext.VersionedText
23 | Meta string
24 | RenderedPage string
25 | IsLocked bool
26 | PassphraseToUnlock string
27 | IsEncrypted bool
28 | IsPrimedForSelfDestruct bool
29 | IsPublished bool
30 | UnlockedFor string
31 | }
32 |
33 | func (p Page) LastEditTime() time.Time {
34 | return time.Unix(p.LastEditUnixTime(), 0)
35 | }
36 |
37 | func (p Page) LastEditUnixTime() int64 {
38 | return p.Text.LastEditTime() / 1000000000
39 | }
40 |
41 | func (s *Site) Open(name string) (p *Page) {
42 | p = new(Page)
43 | p.Site = s
44 | p.Name = name
45 | p.Text = versionedtext.NewVersionedText("")
46 | p.Render()
47 | bJSON, err := ioutil.ReadFile(path.Join(s.PathToData, encodeToBase32(strings.ToLower(name))+".json"))
48 | if err != nil {
49 | return
50 | }
51 | err = json.Unmarshal(bJSON, &p)
52 | if err != nil {
53 | p = new(Page)
54 | }
55 | return p
56 | }
57 |
58 | type DirectoryEntry struct {
59 | Path string
60 | Length int
61 | Numchanges int
62 | LastEdited time.Time
63 | }
64 |
65 | func (d DirectoryEntry) LastEditTime() string {
66 | return d.LastEdited.Format("Mon Jan 2 15:04:05 MST 2006")
67 | }
68 |
69 | func (d DirectoryEntry) Name() string {
70 | return d.Path
71 | }
72 |
73 | func (d DirectoryEntry) Size() int64 {
74 | return int64(d.Length)
75 | }
76 |
77 | func (d DirectoryEntry) Mode() os.FileMode {
78 | return os.ModePerm
79 | }
80 |
81 | func (d DirectoryEntry) ModTime() time.Time {
82 | return d.LastEdited
83 | }
84 |
85 | func (d DirectoryEntry) IsDir() bool {
86 | return false
87 | }
88 |
89 | func (d DirectoryEntry) Sys() interface{} {
90 | return nil
91 | }
92 |
93 | func (s *Site) DirectoryList() []os.FileInfo {
94 | files, _ := ioutil.ReadDir(s.PathToData)
95 | entries := make([]os.FileInfo, len(files))
96 | for i, f := range files {
97 | name := DecodeFileName(f.Name())
98 | p := s.Open(name)
99 | entries[i] = DirectoryEntry{
100 | Path: name,
101 | Length: len(p.Text.GetCurrent()),
102 | Numchanges: p.Text.NumEdits(),
103 | LastEdited: time.Unix(p.Text.LastEditTime()/1000000000, 0),
104 | }
105 | }
106 | sort.Slice(entries, func(i, j int) bool { return entries[i].ModTime().After(entries[j].ModTime()) })
107 | return entries
108 | }
109 |
110 | type UploadEntry struct {
111 | os.FileInfo
112 | }
113 |
114 | func (s *Site) UploadList() ([]os.FileInfo, error) {
115 | paths, err := filepath.Glob(path.Join(s.PathToData, "sha256*"))
116 | if err != nil {
117 | return nil, err
118 | }
119 | result := make([]os.FileInfo, len(paths))
120 | for i := range paths {
121 | result[i], err = os.Stat(paths[i])
122 | if err != nil {
123 | return result, err
124 | }
125 | }
126 | return result, nil
127 | }
128 |
129 | func DecodeFileName(s string) string {
130 | s2, _ := decodeFromBase32(strings.Split(s, ".")[0])
131 | return s2
132 | }
133 |
134 | // Update cleans the text and updates the versioned text
135 | // and generates a new render
136 | func (p *Page) Update(newText string) error {
137 | // Trim space from end
138 | newText = strings.TrimRight(newText, "\n\t ")
139 |
140 | // Update the versioned text
141 | p.Text.Update(newText)
142 |
143 | // Render the new page
144 | p.Render()
145 |
146 | return p.Save()
147 | }
148 |
149 | var rBracketPage = regexp.MustCompile(`\[\[(.*?)\]\]`)
150 |
151 | func (p *Page) Render() {
152 | if p.IsEncrypted {
153 | p.RenderedPage = "" + p.Text.GetCurrent() + "
"
154 | return
155 | }
156 |
157 | // Convert [[page]] to [page](/page/view)
158 | currentText := p.Text.GetCurrent()
159 | for _, s := range rBracketPage.FindAllString(currentText, -1) {
160 | currentText = strings.Replace(currentText, s, "["+s[2:len(s)-2]+"](/"+s[2:len(s)-2]+"/view)", 1)
161 | }
162 | p.Text.Update(currentText)
163 | p.RenderedPage = MarkdownToHtml(p.Text.GetCurrent())
164 | }
165 |
166 | func (p *Page) Save() error {
167 | p.Site.saveMut.Lock()
168 | defer p.Site.saveMut.Unlock()
169 | bJSON, err := json.MarshalIndent(p, "", " ")
170 | if err != nil {
171 | return err
172 | }
173 | return ioutil.WriteFile(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"), bJSON, 0644)
174 | }
175 |
176 | func (p *Page) ChildPageNames() []string {
177 | prefix := strings.ToLower(p.Name + ": ")
178 | files, err := filepath.Glob(path.Join(p.Site.PathToData, "*"))
179 | if err != nil {
180 | panic("Filepath pattern cannot be malformed")
181 | }
182 |
183 | result := []string{}
184 | for i := range files {
185 | basename := filepath.Base(files[i])
186 | if strings.HasSuffix(basename, ".json") {
187 | cname, err := decodeFromBase32(basename[:len(basename)-len(".json")])
188 | if err == nil && strings.HasPrefix(strings.ToLower(cname), prefix) {
189 | result = append(result, cname)
190 | }
191 | }
192 | }
193 | return result
194 | }
195 |
196 | func (p *Page) IsNew() bool {
197 | return !exists(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"))
198 | }
199 |
200 | func (p *Page) Erase() error {
201 | p.Site.Logger.Trace("Erasing " + p.Name)
202 | return os.Remove(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"))
203 | }
204 |
205 | func (p *Page) Published() bool {
206 | return p.IsPublished
207 | }
208 |
--------------------------------------------------------------------------------
/server/page_test.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "os"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func TestListFiles(t *testing.T) {
10 | pathToData = "testdata"
11 | os.MkdirAll(pathToData, 0755)
12 | defer os.RemoveAll(pathToData)
13 | s := Site{PathToData: pathToData}
14 | p := s.Open("testpage")
15 | p.Update("Some data")
16 | p = s.Open("testpage2")
17 | p.Update("A different bunch of data")
18 | p = s.Open("testpage3")
19 | p.Update("Not much else")
20 | n := s.DirectoryList()
21 | if len(n) != 3 {
22 | t.Error("Expected three directory entries")
23 | t.FailNow()
24 | }
25 | if n[0].Name() != "testpage" {
26 | t.Error("Expected testpage to be first")
27 | }
28 | if n[1].Name() != "testpage2" {
29 | t.Error("Expected testpage2 to be second")
30 | }
31 | if n[2].Name() != "testpage3" {
32 | t.Error("Expected testpage3 to be last")
33 | }
34 | }
35 |
36 | func TestGeneral(t *testing.T) {
37 | pathToData = "testdata"
38 | os.MkdirAll(pathToData, 0755)
39 | defer os.RemoveAll(pathToData)
40 | s := Site{PathToData: pathToData}
41 | p := s.Open("testpage")
42 | err := p.Update("**bold**")
43 | if err != nil {
44 | t.Error(err)
45 | }
46 | if strings.TrimSpace(p.RenderedPage) != "bold
" {
47 | t.Errorf("Did not render: '%s'", p.RenderedPage)
48 | }
49 | err = p.Update("**bold** and *italic*")
50 | if err != nil {
51 | t.Error(err)
52 | }
53 | p.Save()
54 |
55 | p2 := s.Open("testpage")
56 | if strings.TrimSpace(p2.RenderedPage) != "bold and italic
" {
57 | t.Errorf("Did not render: '%s'", p2.RenderedPage)
58 | }
59 |
60 | p3 := s.Open("testpage: childpage")
61 | err = p3.Update("**child content**")
62 | if err != nil {
63 | t.Error(err)
64 | }
65 |
66 | children := p.ChildPageNames()
67 | if len(children) != 1 {
68 | t.Errorf("Expected 1 child page to be found, got %d", len(children))
69 | return
70 | }
71 | if children[0] != "testpage: childpage" {
72 | t.Errorf("Expected child page %s to be found (got %s)", "testpage: childpage", children[0])
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/server/utils.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "encoding/base32"
5 | "encoding/binary"
6 | "encoding/hex"
7 | "math/rand"
8 | "net/http"
9 | "os"
10 | "path"
11 | "strings"
12 | "time"
13 |
14 | "github.com/microcosm-cc/bluemonday"
15 | "github.com/russross/blackfriday/v2"
16 | "github.com/shurcooL/github_flavored_markdown"
17 | "golang.org/x/crypto/bcrypt"
18 | )
19 |
20 | var animals []string
21 | var adjectives []string
22 | var aboutPageText string
23 | var allowInsecureHtml bool
24 |
25 | func init() {
26 | rand.Seed(time.Now().Unix())
27 | animalsText, _ := Asset("static/text/animals")
28 | animals = strings.Split(string(animalsText), ",")
29 | adjectivesText, _ := Asset("static/text/adjectives")
30 | adjectives = strings.Split(string(adjectivesText), "\n")
31 | }
32 |
33 | func randomAnimal() string {
34 | return strings.Replace(strings.Title(animals[rand.Intn(len(animals)-1)]), " ", "", -1)
35 | }
36 |
37 | func randomAdjective() string {
38 | return strings.Replace(strings.Title(adjectives[rand.Intn(len(adjectives)-1)]), " ", "", -1)
39 | }
40 |
41 | func randomAlliterateCombo() (combo string) {
42 | combo = ""
43 | // generate random alliteration thats not been used
44 | for {
45 | animal := randomAnimal()
46 | adjective := randomAdjective()
47 | if animal[0] == adjective[0] && len(animal)+len(adjective) < 18 { //&& stringInSlice(strings.ToLower(adjective+animal), takenNames) == false {
48 | combo = adjective + animal
49 | break
50 | }
51 | }
52 | return
53 | }
54 |
55 | // is there a string in a slice?
56 | func stringInSlice(s string, strings []string) bool {
57 | for _, k := range strings {
58 | if s == k {
59 | return true
60 | }
61 | }
62 | return false
63 | }
64 |
65 | // itob returns an 8-byte big endian representation of v.
66 | func itob(v int) []byte {
67 | b := make([]byte, 8)
68 | binary.BigEndian.PutUint64(b, uint64(v))
69 | return b
70 | }
71 |
72 | func contentType(filename string) string {
73 | switch {
74 | case strings.Contains(filename, ".css"):
75 | return "text/css"
76 | case strings.Contains(filename, ".jpg"):
77 | return "image/jpeg"
78 | case strings.Contains(filename, ".png"):
79 | return "image/png"
80 | case strings.Contains(filename, ".js"):
81 | return "application/javascript"
82 | case strings.Contains(filename, ".xml"):
83 | return "application/xml"
84 | }
85 | return "text/html"
86 | }
87 |
88 | func (s *Site) sniffContentType(name string) (string, error) {
89 | file, err := os.Open(path.Join(s.PathToData, name))
90 | if err != nil {
91 | return "", err
92 |
93 | }
94 | defer file.Close()
95 |
96 | // Only the first 512 bytes are used to sniff the content type.
97 | buffer := make([]byte, 512)
98 | _, err = file.Read(buffer)
99 | if err != nil {
100 | return "", err
101 | }
102 |
103 | // Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
104 | return http.DetectContentType(buffer), nil
105 | }
106 |
107 | var src = rand.NewSource(time.Now().UnixNano())
108 |
109 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
110 | const (
111 | letterIdxBits = 6 // 6 bits to represent a letter index
112 | letterIdxMask = 1<= 0; {
121 | if remain == 0 {
122 | cache, remain = src.Int63(), letterIdxMax
123 | }
124 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
125 | b[i] = letterBytes[idx]
126 | i--
127 | }
128 | cache >>= letterIdxBits
129 | remain--
130 | }
131 |
132 | return string(b)
133 | }
134 |
135 | // HashPassword generates a bcrypt hash of the password using work factor 14.
136 | // https://github.com/gtank/cryptopasta/blob/master/hash.go
137 | func HashPassword(password string) string {
138 | hash, _ := bcrypt.GenerateFromPassword([]byte(password), 14)
139 | return hex.EncodeToString(hash)
140 | }
141 |
142 | // CheckPassword securely compares a bcrypt hashed password with its possible
143 | // plaintext equivalent. Returns nil on success, or an error on failure.
144 | // https://github.com/gtank/cryptopasta/blob/master/hash.go
145 | func CheckPasswordHash(password, hashedString string) error {
146 | hash, err := hex.DecodeString(hashedString)
147 | if err != nil {
148 | return err
149 | }
150 | return bcrypt.CompareHashAndPassword(hash, []byte(password))
151 | }
152 |
153 | // exists returns whether the given file or directory exists or not
154 | func exists(path string) bool {
155 | _, err := os.Stat(path)
156 | return !os.IsNotExist(err)
157 | }
158 |
159 | func MarkdownToHtml(s string) string {
160 | unsafe := blackfriday.Run([]byte(s))
161 | if allowInsecureHtml {
162 | return string(unsafe)
163 | }
164 |
165 | pClean := bluemonday.UGCPolicy()
166 | pClean.AllowElements("img")
167 | pClean.AllowElements("center")
168 | pClean.AllowAttrs("alt").OnElements("img")
169 | pClean.AllowAttrs("src").OnElements("img")
170 | pClean.AllowAttrs("class").OnElements("a")
171 | pClean.AllowAttrs("href").OnElements("a")
172 | pClean.AllowAttrs("id").OnElements("a")
173 | pClean.AllowDataURIImages()
174 | html := pClean.SanitizeBytes(unsafe)
175 | return string(html)
176 | }
177 |
178 | func GithubMarkdownToHTML(s string) string {
179 | return string(github_flavored_markdown.Markdown([]byte(s)))
180 | }
181 |
182 | func encodeToBase32(s string) string {
183 | return encodeBytesToBase32([]byte(s))
184 | }
185 |
186 | func encodeBytesToBase32(s []byte) string {
187 | return base32.StdEncoding.EncodeToString(s)
188 | }
189 |
190 | func decodeFromBase32(s string) (s2 string, err error) {
191 | bString, err := base32.StdEncoding.DecodeString(s)
192 | s2 = string(bString)
193 | return
194 | }
195 |
196 | func reverseSliceInt64(s []int64) []int64 {
197 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
198 | s[i], s[j] = s[j], s[i]
199 | }
200 | return s
201 | }
202 |
203 | func reverseSliceString(s []string) []string {
204 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
205 | s[i], s[j] = s[j], s[i]
206 | }
207 | return s
208 | }
209 |
210 | func reverseSliceInt(s []int) []int {
211 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
212 | s[i], s[j] = s[j], s[i]
213 | }
214 | return s
215 | }
216 |
--------------------------------------------------------------------------------
/server/utils_test.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func BenchmarkAlliterativeAnimal(b *testing.B) {
8 | for i := 0; i < b.N; i++ {
9 | randomAlliterateCombo()
10 | }
11 | }
12 |
13 | func TestReverseList(t *testing.T) {
14 | s := []int64{1, 10, 2, 20}
15 | if reverseSliceInt64(s)[0] != 20 {
16 | t.Errorf("Could not reverse: %v", s)
17 | }
18 | s2 := []string{"a", "b", "d", "c"}
19 | if reverseSliceString(s2)[0] != "c" {
20 | t.Errorf("Could not reverse: %v", s2)
21 | }
22 | }
23 |
24 | func TestHashing(t *testing.T) {
25 | p := HashPassword("1234")
26 | err := CheckPasswordHash("1234", p)
27 | if err != nil {
28 | t.Errorf("Should be correct password")
29 | }
30 | err = CheckPasswordHash("1234lkjklj", p)
31 | if err == nil {
32 | t.Errorf("Should NOT be correct password")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/static/css/base-min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Pure v0.6.2
3 | Copyright 2013 Yahoo!
4 | Licensed under the BSD License.
5 | https://github.com/yahoo/pure/blob/master/LICENSE.md
6 | */
7 | /*!
8 | normalize.css v^3.0 | MIT License | git.io/normalize
9 | Copyright (c) Nicolas Gallagher and Jonathan Neal
10 | */
11 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */img,legend{border:0}legend,td,th{padding:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}table{border-collapse:collapse;border-spacing:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}
--------------------------------------------------------------------------------
/static/css/default.css:
--------------------------------------------------------------------------------
1 | body.ListPage span {
2 | cursor: pointer;
3 | }
4 | body {
5 | background: #fff;
6 | }
7 | .success {
8 | color: #5cb85c;
9 | font-weight: bold;
10 | }
11 | .failure {
12 | color: #d9534f;
13 | font-weight: bold;
14 | }
15 | .pure-menu a {
16 | color: #777;
17 | }
18 | .deleting {
19 | opacity: 0.5;
20 | }
21 | #wrap {
22 | position: absolute;
23 | top: 50px;
24 | left: 0px;
25 | right: 0px;
26 | bottom: 0px;
27 | }
28 | #pad {
29 | height:100%;
30 | }
31 |
32 | body.EditPage {
33 | overflow:hidden;
34 | }
35 |
36 | body#pad textarea {
37 | width: 100%;
38 | height: 100%;
39 | box-sizing: border-box;
40 | -moz-box-sizing: border-box;
41 | -webkit-box-sizing: border-box;
42 | position: absolute;
43 | left: 0;
44 | right: 0;
45 | bottom: 0;
46 | top: 0;
47 | border: 0;
48 | border: none;
49 | outline: none;
50 | -webkit-box-shadow: none;
51 | -moz-box-shadow: none;
52 | box-shadow: none;
53 | resize: none;
54 | font-size: 1.0em;
55 | font-family: 'Open Sans','Segoe UI',Tahoma,Arial,sans-serif;
56 | }
57 |
58 | body#pad.HasDotInName textarea {
59 | font-family: "Lucida Console", Monaco, monospace;
60 | }
61 |
62 | .markdown-body ul, .markdown-body ol {
63 | padding-left: 0em;
64 | }
65 |
66 | @media (min-width: 5em) {
67 | div#menu, div#rendered, .ChildPageNames, body#pad textarea {
68 | padding-left: 2%;
69 | padding-right: 2%;
70 | }
71 | .pure-menu .pure-menu-horizontal {
72 | max-width: 300px;
73 | }
74 | .pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
75 | padding-left:1.2em;
76 | padding-right:em;
77 | }
78 | .ChildPageNames ul {
79 | grid-template-columns: repeat(1, 1fr);
80 | }
81 | }
82 | @media (min-width: 50em) {
83 | div#menu, div#rendered, .ChildPageNames, body#pad textarea {
84 | padding-left: 10%;
85 | padding-right: 10%;
86 | }
87 | .pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
88 | padding: .5em 1em;
89 | }
90 | .ChildPageNames ul {
91 | grid-template-columns: repeat(2, 1fr);
92 | }
93 | }
94 | @media (min-width: 70em) {
95 | div#menu, div#rendered, .ChildPageNames, body#pad textarea {
96 | padding-left: 15%;
97 | padding-right: 15%;
98 | }
99 | .ChildPageNames ul {
100 | grid-template-columns: repeat(3, 1fr);
101 | }
102 | }
103 |
104 | @media (min-width: 100em) {
105 | div#menu, div#rendered, .ChildPageNames, body#pad textarea {
106 | padding-left: 20%;
107 | padding-right: 20%;
108 | }
109 | .ChildPageNames ul {
110 | grid-template-columns: repeat(4, 1fr);
111 | }
112 | }
113 |
114 | .ChildPageNames ul {
115 | margin: 0.3em 0 0 1.6em;
116 | padding: 0;
117 | display: grid;
118 | grid-gap: 0.5rem;
119 | }
120 |
121 | .ChildPageNames li {
122 | margin: 0;
123 | }
124 |
125 | .ChildPageNames a {
126 | color: #0645ad;
127 | background: none;
128 | }
129 |
--------------------------------------------------------------------------------
/static/css/dropzone.css:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | * Copyright (c) 2012 Matias Meno
4 | */
5 | @-webkit-keyframes passing-through {
6 | 0% {
7 | opacity: 0;
8 | -webkit-transform: translateY(40px);
9 | -moz-transform: translateY(40px);
10 | -ms-transform: translateY(40px);
11 | -o-transform: translateY(40px);
12 | transform: translateY(40px); }
13 | 30%, 70% {
14 | opacity: 1;
15 | -webkit-transform: translateY(0px);
16 | -moz-transform: translateY(0px);
17 | -ms-transform: translateY(0px);
18 | -o-transform: translateY(0px);
19 | transform: translateY(0px); }
20 | 100% {
21 | opacity: 0;
22 | -webkit-transform: translateY(-40px);
23 | -moz-transform: translateY(-40px);
24 | -ms-transform: translateY(-40px);
25 | -o-transform: translateY(-40px);
26 | transform: translateY(-40px); } }
27 | @-moz-keyframes passing-through {
28 | 0% {
29 | opacity: 0;
30 | -webkit-transform: translateY(40px);
31 | -moz-transform: translateY(40px);
32 | -ms-transform: translateY(40px);
33 | -o-transform: translateY(40px);
34 | transform: translateY(40px); }
35 | 30%, 70% {
36 | opacity: 1;
37 | -webkit-transform: translateY(0px);
38 | -moz-transform: translateY(0px);
39 | -ms-transform: translateY(0px);
40 | -o-transform: translateY(0px);
41 | transform: translateY(0px); }
42 | 100% {
43 | opacity: 0;
44 | -webkit-transform: translateY(-40px);
45 | -moz-transform: translateY(-40px);
46 | -ms-transform: translateY(-40px);
47 | -o-transform: translateY(-40px);
48 | transform: translateY(-40px); } }
49 | @keyframes passing-through {
50 | 0% {
51 | opacity: 0;
52 | -webkit-transform: translateY(40px);
53 | -moz-transform: translateY(40px);
54 | -ms-transform: translateY(40px);
55 | -o-transform: translateY(40px);
56 | transform: translateY(40px); }
57 | 30%, 70% {
58 | opacity: 1;
59 | -webkit-transform: translateY(0px);
60 | -moz-transform: translateY(0px);
61 | -ms-transform: translateY(0px);
62 | -o-transform: translateY(0px);
63 | transform: translateY(0px); }
64 | 100% {
65 | opacity: 0;
66 | -webkit-transform: translateY(-40px);
67 | -moz-transform: translateY(-40px);
68 | -ms-transform: translateY(-40px);
69 | -o-transform: translateY(-40px);
70 | transform: translateY(-40px); } }
71 | @-webkit-keyframes slide-in {
72 | 0% {
73 | opacity: 0;
74 | -webkit-transform: translateY(40px);
75 | -moz-transform: translateY(40px);
76 | -ms-transform: translateY(40px);
77 | -o-transform: translateY(40px);
78 | transform: translateY(40px); }
79 | 30% {
80 | opacity: 1;
81 | -webkit-transform: translateY(0px);
82 | -moz-transform: translateY(0px);
83 | -ms-transform: translateY(0px);
84 | -o-transform: translateY(0px);
85 | transform: translateY(0px); } }
86 | @-moz-keyframes slide-in {
87 | 0% {
88 | opacity: 0;
89 | -webkit-transform: translateY(40px);
90 | -moz-transform: translateY(40px);
91 | -ms-transform: translateY(40px);
92 | -o-transform: translateY(40px);
93 | transform: translateY(40px); }
94 | 30% {
95 | opacity: 1;
96 | -webkit-transform: translateY(0px);
97 | -moz-transform: translateY(0px);
98 | -ms-transform: translateY(0px);
99 | -o-transform: translateY(0px);
100 | transform: translateY(0px); } }
101 | @keyframes slide-in {
102 | 0% {
103 | opacity: 0;
104 | -webkit-transform: translateY(40px);
105 | -moz-transform: translateY(40px);
106 | -ms-transform: translateY(40px);
107 | -o-transform: translateY(40px);
108 | transform: translateY(40px); }
109 | 30% {
110 | opacity: 1;
111 | -webkit-transform: translateY(0px);
112 | -moz-transform: translateY(0px);
113 | -ms-transform: translateY(0px);
114 | -o-transform: translateY(0px);
115 | transform: translateY(0px); } }
116 | @-webkit-keyframes pulse {
117 | 0% {
118 | -webkit-transform: scale(1);
119 | -moz-transform: scale(1);
120 | -ms-transform: scale(1);
121 | -o-transform: scale(1);
122 | transform: scale(1); }
123 | 10% {
124 | -webkit-transform: scale(1.1);
125 | -moz-transform: scale(1.1);
126 | -ms-transform: scale(1.1);
127 | -o-transform: scale(1.1);
128 | transform: scale(1.1); }
129 | 20% {
130 | -webkit-transform: scale(1);
131 | -moz-transform: scale(1);
132 | -ms-transform: scale(1);
133 | -o-transform: scale(1);
134 | transform: scale(1); } }
135 | @-moz-keyframes pulse {
136 | 0% {
137 | -webkit-transform: scale(1);
138 | -moz-transform: scale(1);
139 | -ms-transform: scale(1);
140 | -o-transform: scale(1);
141 | transform: scale(1); }
142 | 10% {
143 | -webkit-transform: scale(1.1);
144 | -moz-transform: scale(1.1);
145 | -ms-transform: scale(1.1);
146 | -o-transform: scale(1.1);
147 | transform: scale(1.1); }
148 | 20% {
149 | -webkit-transform: scale(1);
150 | -moz-transform: scale(1);
151 | -ms-transform: scale(1);
152 | -o-transform: scale(1);
153 | transform: scale(1); } }
154 | @keyframes pulse {
155 | 0% {
156 | -webkit-transform: scale(1);
157 | -moz-transform: scale(1);
158 | -ms-transform: scale(1);
159 | -o-transform: scale(1);
160 | transform: scale(1); }
161 | 10% {
162 | -webkit-transform: scale(1.1);
163 | -moz-transform: scale(1.1);
164 | -ms-transform: scale(1.1);
165 | -o-transform: scale(1.1);
166 | transform: scale(1.1); }
167 | 20% {
168 | -webkit-transform: scale(1);
169 | -moz-transform: scale(1);
170 | -ms-transform: scale(1);
171 | -o-transform: scale(1);
172 | transform: scale(1); } }
173 | .dropzone, .dropzone * {
174 | box-sizing: border-box; }
175 |
176 | .dropzone {
177 | min-height: 150px;
178 | border: 2px solid rgba(0, 0, 0, 0.3);
179 | background: white;
180 | padding: 20px 20px; }
181 | .dropzone.dz-clickable {
182 | cursor: pointer; }
183 | .dropzone.dz-clickable * {
184 | cursor: default; }
185 | .dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * {
186 | cursor: pointer; }
187 | .dropzone.dz-started .dz-message {
188 | display: none; }
189 | .dropzone.dz-drag-hover {
190 | border-style: solid; }
191 | .dropzone.dz-drag-hover .dz-message {
192 | opacity: 0.5; }
193 | .dropzone .dz-message {
194 | text-align: center;
195 | margin: 2em 0; }
196 | .dropzone .dz-preview {
197 | position: relative;
198 | display: inline-block;
199 | vertical-align: top;
200 | margin: 16px;
201 | min-height: 100px; }
202 | .dropzone .dz-preview:hover {
203 | z-index: 1000; }
204 | .dropzone .dz-preview:hover .dz-details {
205 | opacity: 1; }
206 | .dropzone .dz-preview.dz-file-preview .dz-image {
207 | border-radius: 20px;
208 | background: #999;
209 | background: linear-gradient(to bottom, #eee, #ddd); }
210 | .dropzone .dz-preview.dz-file-preview .dz-details {
211 | opacity: 1; }
212 | .dropzone .dz-preview.dz-image-preview {
213 | background: white; }
214 | .dropzone .dz-preview.dz-image-preview .dz-details {
215 | -webkit-transition: opacity 0.2s linear;
216 | -moz-transition: opacity 0.2s linear;
217 | -ms-transition: opacity 0.2s linear;
218 | -o-transition: opacity 0.2s linear;
219 | transition: opacity 0.2s linear; }
220 | .dropzone .dz-preview .dz-remove {
221 | font-size: 14px;
222 | text-align: center;
223 | display: block;
224 | cursor: pointer;
225 | border: none; }
226 | .dropzone .dz-preview .dz-remove:hover {
227 | text-decoration: underline; }
228 | .dropzone .dz-preview:hover .dz-details {
229 | opacity: 1; }
230 | .dropzone .dz-preview .dz-details {
231 | z-index: 20;
232 | position: absolute;
233 | top: 0;
234 | left: 0;
235 | opacity: 0;
236 | font-size: 13px;
237 | min-width: 100%;
238 | max-width: 100%;
239 | padding: 2em 1em;
240 | text-align: center;
241 | color: rgba(0, 0, 0, 0.9);
242 | line-height: 150%; }
243 | .dropzone .dz-preview .dz-details .dz-size {
244 | margin-bottom: 1em;
245 | font-size: 16px; }
246 | .dropzone .dz-preview .dz-details .dz-filename {
247 | white-space: nowrap; }
248 | .dropzone .dz-preview .dz-details .dz-filename:hover span {
249 | border: 1px solid rgba(200, 200, 200, 0.8);
250 | background-color: rgba(255, 255, 255, 0.8); }
251 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) {
252 | overflow: hidden;
253 | text-overflow: ellipsis; }
254 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
255 | border: 1px solid transparent; }
256 | .dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
257 | background-color: rgba(255, 255, 255, 0.4);
258 | padding: 0 0.4em;
259 | border-radius: 3px; }
260 | .dropzone .dz-preview:hover .dz-image img {
261 | -webkit-transform: scale(1.05, 1.05);
262 | -moz-transform: scale(1.05, 1.05);
263 | -ms-transform: scale(1.05, 1.05);
264 | -o-transform: scale(1.05, 1.05);
265 | transform: scale(1.05, 1.05);
266 | -webkit-filter: blur(8px);
267 | filter: blur(8px); }
268 | .dropzone .dz-preview .dz-image {
269 | border-radius: 20px;
270 | overflow: hidden;
271 | width: 120px;
272 | height: 120px;
273 | position: relative;
274 | display: block;
275 | z-index: 10; }
276 | .dropzone .dz-preview .dz-image img {
277 | display: block; }
278 | .dropzone .dz-preview.dz-success .dz-success-mark {
279 | -webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
280 | -moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
281 | -ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
282 | -o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
283 | animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); }
284 | .dropzone .dz-preview.dz-error .dz-error-mark {
285 | opacity: 1;
286 | -webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
287 | -moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
288 | -ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
289 | -o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
290 | animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); }
291 | .dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
292 | pointer-events: none;
293 | opacity: 0;
294 | z-index: 500;
295 | position: absolute;
296 | display: block;
297 | top: 50%;
298 | left: 50%;
299 | margin-left: -27px;
300 | margin-top: -27px; }
301 | .dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg {
302 | display: block;
303 | width: 54px;
304 | height: 54px; }
305 | .dropzone .dz-preview.dz-processing .dz-progress {
306 | opacity: 1;
307 | -webkit-transition: all 0.2s linear;
308 | -moz-transition: all 0.2s linear;
309 | -ms-transition: all 0.2s linear;
310 | -o-transition: all 0.2s linear;
311 | transition: all 0.2s linear; }
312 | .dropzone .dz-preview.dz-complete .dz-progress {
313 | opacity: 0;
314 | -webkit-transition: opacity 0.4s ease-in;
315 | -moz-transition: opacity 0.4s ease-in;
316 | -ms-transition: opacity 0.4s ease-in;
317 | -o-transition: opacity 0.4s ease-in;
318 | transition: opacity 0.4s ease-in; }
319 | .dropzone .dz-preview:not(.dz-processing) .dz-progress {
320 | -webkit-animation: pulse 6s ease infinite;
321 | -moz-animation: pulse 6s ease infinite;
322 | -ms-animation: pulse 6s ease infinite;
323 | -o-animation: pulse 6s ease infinite;
324 | animation: pulse 6s ease infinite; }
325 | .dropzone .dz-preview .dz-progress {
326 | opacity: 1;
327 | z-index: 1000;
328 | pointer-events: none;
329 | position: absolute;
330 | height: 16px;
331 | left: 50%;
332 | top: 50%;
333 | margin-top: -8px;
334 | width: 80px;
335 | margin-left: -40px;
336 | background: rgba(255, 255, 255, 0.9);
337 | -webkit-transform: scale(1);
338 | border-radius: 8px;
339 | overflow: hidden; }
340 | .dropzone .dz-preview .dz-progress .dz-upload {
341 | background: #333;
342 | background: linear-gradient(to bottom, #666, #444);
343 | position: absolute;
344 | top: 0;
345 | left: 0;
346 | bottom: 0;
347 | width: 0;
348 | -webkit-transition: width 300ms ease-in-out;
349 | -moz-transition: width 300ms ease-in-out;
350 | -ms-transition: width 300ms ease-in-out;
351 | -o-transition: width 300ms ease-in-out;
352 | transition: width 300ms ease-in-out; }
353 | .dropzone .dz-preview.dz-error .dz-error-message {
354 | display: block; }
355 | .dropzone .dz-preview.dz-error:hover .dz-error-message {
356 | opacity: 1;
357 | pointer-events: auto; }
358 | .dropzone .dz-preview .dz-error-message {
359 | pointer-events: none;
360 | z-index: 1000;
361 | position: absolute;
362 | display: block;
363 | display: none;
364 | opacity: 0;
365 | -webkit-transition: opacity 0.3s ease;
366 | -moz-transition: opacity 0.3s ease;
367 | -ms-transition: opacity 0.3s ease;
368 | -o-transition: opacity 0.3s ease;
369 | transition: opacity 0.3s ease;
370 | border-radius: 8px;
371 | font-size: 13px;
372 | top: 130px;
373 | left: -10px;
374 | width: 140px;
375 | background: #be2626;
376 | background: linear-gradient(to bottom, #be2626, #a92222);
377 | padding: 0.5em 1.2em;
378 | color: white; }
379 | .dropzone .dz-preview .dz-error-message:after {
380 | content: '';
381 | position: absolute;
382 | top: -6px;
383 | left: 64px;
384 | width: 0;
385 | height: 0;
386 | border-left: 6px solid transparent;
387 | border-right: 6px solid transparent;
388 | border-bottom: 6px solid #be2626; }
389 |
--------------------------------------------------------------------------------
/static/css/github-markdown.css:
--------------------------------------------------------------------------------
1 | @font-face{font-family:octicons-link;src:url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');}.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.5;color:#333;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:16px;line-height:1.5;word-wrap:break-word;}.markdown-body .pl-c{color:#969896;}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:#0086b3;}.markdown-body .pl-e,.markdown-body .pl-en{color:#795da3;}.markdown-body .pl-smi,.markdown-body .pl-s .pl-s1{color:#333;}.markdown-body .pl-ent{color:#63a35c;}.markdown-body .pl-k{color:#a71d5d;}.markdown-body .pl-s,.markdown-body .pl-pds,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sre,.markdown-body .pl-sr .pl-sra{color:#183691;}.markdown-body .pl-v{color:#ed6a43;}.markdown-body .pl-id{color:#b52a1d;}.markdown-body .pl-ii{color:#f8f8f8;background-color:#b52a1d;}.markdown-body .pl-sr .pl-cce{font-weight:bold;color:#63a35c;}.markdown-body .pl-ml{color:#693a17;}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{font-weight:bold;color:#1d3e81;}.markdown-body .pl-mq{color:#008080;}.markdown-body .pl-mi{font-style:italic;color:#333;}.markdown-body .pl-mb{font-weight:bold;color:#333;}.markdown-body .pl-md{color:#bd2c00;background-color:#ffecec;}.markdown-body .pl-mi1{color:#55a532;background-color:#eaffea;}.markdown-body .pl-mdr{font-weight:bold;color:#795da3;}.markdown-body .pl-mo{color:#1d3e81;}.markdown-body .octicon{display:inline-block;vertical-align:text-top;fill:currentColor;}.markdown-body a{background-color:transparent;-webkit-text-decoration-skip:objects;}.markdown-body a:active,.markdown-body a:hover{outline-width:0;}.markdown-body strong{font-weight:inherit;}.markdown-body strong{font-weight:bolder;}.markdown-body h1{font-size:2em;margin:0.67em 0;}.markdown-body img{border-style:none;}.markdown-body svg:not(:root){overflow:hidden;}.markdown-body code,.markdown-body kbd,.markdown-body pre{font-family:monospace,monospace;font-size:1em;}.markdown-body hr{box-sizing:content-box;height:0;overflow:visible;}.markdown-body input{font:inherit;margin:0;}.markdown-body input{overflow:visible;}.markdown-body [type="checkbox"]{box-sizing:border-box;padding:0;}.markdown-body *{box-sizing:border-box;}.markdown-body input{font-family:inherit;font-size:inherit;line-height:inherit;}.markdown-body a{color:#4078c0;text-decoration:none;}.markdown-body a:hover,.markdown-body a:active{text-decoration:underline;}.markdown-body strong{font-weight:600;}.markdown-body hr{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #ddd;}.markdown-body hr::before{display:table;content:"";}.markdown-body hr::after{display:table;clear:both;content:"";}.markdown-body table{border-spacing:0;border-collapse:collapse;}.markdown-body td,.markdown-body th{padding:0;}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:0;margin-bottom:0;}.markdown-body h1{font-size:32px;font-weight:600;}.markdown-body h2{font-size:24px;font-weight:600;}.markdown-body h3{font-size:20px;font-weight:600;}.markdown-body h4{font-size:16px;font-weight:600;}.markdown-body h5{font-size:14px;font-weight:600;}.markdown-body h6{font-size:12px;font-weight:600;}.markdown-body p{margin-top:0;margin-bottom:10px;}.markdown-body blockquote{margin:0;}.markdown-body ul,.markdown-body ol{padding-left:0;margin-top:0;margin-bottom:0;}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman;}.markdown-body ul ul ol,.markdown-body ul ol ol,.markdown-body ol ul ol,.markdown-body ol ol ol{list-style-type:lower-alpha;}.markdown-body dd{margin-left:0;}.markdown-body code{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px;}.markdown-body pre{margin-top:0;margin-bottom:0;font:12px Consolas,"Liberation Mono",Menlo,Courier,monospace;}.markdown-body .octicon{vertical-align:text-bottom;}.markdown-body input{-webkit-font-feature-settings:"liga" 0;font-feature-settings:"liga" 0;}.markdown-body::before{display:table;content:"";}.markdown-body::after{display:table;clear:both;content:"";}.markdown-body>*:first-child{margin-top:0!important;}.markdown-body>*:last-child{margin-bottom:0!important;}.markdown-body a:not([href]){color:inherit;text-decoration:none;}.markdown-body .anchor{float:left;padding-right:4px;margin-left:-20px;line-height:1;}.markdown-body .anchor:focus{outline:none;}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre{margin-top:0;margin-bottom:16px;}.markdown-body hr{height:0.25em;padding:0;margin:24px 0;background-color:#e7e7e7;border:0;}.markdown-body blockquote{padding:0 1em;color:#777;border-left:0.25em solid #ddd;}.markdown-body blockquote>:first-child{margin-top:0;}.markdown-body blockquote>:last-child{margin-bottom:0;}.markdown-body kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:solid 1px #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb;}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25;}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:#000;vertical-align:middle;visibility:hidden;}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none;}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible;}.markdown-body h1{padding-bottom:0.3em;font-size:2em;border-bottom:1px solid #eee;}.markdown-body h2{padding-bottom:0.3em;font-size:1.5em;border-bottom:1px solid #eee;}.markdown-body h3{font-size:1.25em;}.markdown-body h4{font-size:1em;}.markdown-body h5{font-size:0.875em;}.markdown-body h6{font-size:0.85em;color:#777;}.markdown-body ul,.markdown-body ol{padding-left:2em;}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0;}.markdown-body li>p{margin-top:16px;}.markdown-body li+li{margin-top:0.25em;}.markdown-body dl{padding:0;}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:bold;}.markdown-body dl dd{padding:0 16px;margin-bottom:16px;}.markdown-body table{display:block;width:100%;overflow:auto;}.markdown-body table th{font-weight:bold;}.markdown-body table th,.markdown-body table td{padding:6px 13px;border:1px solid #ddd;}.markdown-body table tr{background-color:#fff;border-top:1px solid #ccc;}.markdown-body table tr:nth-child(2n){background-color:#f8f8f8;}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:#fff;}.markdown-body code{padding:0;padding-top:0.2em;padding-bottom:0.2em;margin:0;font-size:85%;background-color:rgba(0,0,0,0.04);border-radius:3px;}.markdown-body code::before,.markdown-body code::after{letter-spacing:-0.2em;content:"\00a0";}.markdown-body pre{word-wrap:normal;}.markdown-body pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:transparent;border:0;}.markdown-body .highlight{margin-bottom:16px;}.markdown-body .highlight pre{margin-bottom:0;word-break:normal;}.markdown-body .highlight pre,.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border-radius:3px;}.markdown-body pre code{display:inline;max-width:auto;padding:0;margin:0;overflow:visible;line-height:inherit;word-wrap:normal;background-color:transparent;border:0;}.markdown-body pre code::before,.markdown-body pre code::after{content:normal;}.markdown-body .pl-0{padding-left:0!important;}.markdown-body .pl-1{padding-left:3px!important;}.markdown-body .pl-2{padding-left:6px!important;}.markdown-body .pl-3{padding-left:12px!important;}.markdown-body .pl-4{padding-left:24px!important;}.markdown-body .pl-5{padding-left:36px!important;}.markdown-body .pl-6{padding-left:48px!important;}.markdown-body .full-commit .btn-outline:not(:disabled):hover{color:#4078c0;border:1px solid #4078c0;}.markdown-body kbd{display:inline-block;padding:3px 5px;font:11px Consolas,"Liberation Mono",Menlo,Courier,monospace;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:solid 1px #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb;}.markdown-body :checked+.radio-label{position:relative;z-index:1;border-color:#4078c0;}.markdown-body .task-list-item{list-style-type:none;}.markdown-body .task-list-item+.task-list-item{margin-top:3px;}.markdown-body .task-list-item input{margin:0 0.2em 0.25em -1.6em;vertical-align:middle;}.markdown-body hr{border-bottom-color:#eee;}
--------------------------------------------------------------------------------
/static/css/highlight.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
--------------------------------------------------------------------------------
/static/css/menus-min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Pure v0.6.2
3 | Copyright 2013 Yahoo!
4 | Licensed under the BSD License.
5 | https://github.com/yahoo/pure/blob/master/LICENSE.md
6 | */
7 | .pure-menu{box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-disabled,.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}
--------------------------------------------------------------------------------
/static/img/cowyo/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/android-icon-144x144.png
--------------------------------------------------------------------------------
/static/img/cowyo/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/android-icon-192x192.png
--------------------------------------------------------------------------------
/static/img/cowyo/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/android-icon-36x36.png
--------------------------------------------------------------------------------
/static/img/cowyo/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/android-icon-48x48.png
--------------------------------------------------------------------------------
/static/img/cowyo/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/android-icon-72x72.png
--------------------------------------------------------------------------------
/static/img/cowyo/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/android-icon-96x96.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-114x114.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-120x120.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-144x144.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-152x152.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-180x180.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-57x57.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-60x60.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-72x72.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-76x76.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/static/img/cowyo/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/apple-icon.png
--------------------------------------------------------------------------------
/static/img/cowyo/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
--------------------------------------------------------------------------------
/static/img/cowyo/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/favicon-16x16.png
--------------------------------------------------------------------------------
/static/img/cowyo/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/favicon-32x32.png
--------------------------------------------------------------------------------
/static/img/cowyo/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/favicon-96x96.png
--------------------------------------------------------------------------------
/static/img/cowyo/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/favicon.ico
--------------------------------------------------------------------------------
/static/img/cowyo/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "App",
3 | "icons": [
4 | {
5 | "src": "\/android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "\/android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "\/android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "\/android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "\/android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "\/android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/static/img/cowyo/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/ms-icon-144x144.png
--------------------------------------------------------------------------------
/static/img/cowyo/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/ms-icon-150x150.png
--------------------------------------------------------------------------------
/static/img/cowyo/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/ms-icon-310x310.png
--------------------------------------------------------------------------------
/static/img/cowyo/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/cowyo/ms-icon-70x70.png
--------------------------------------------------------------------------------
/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/cowyo/ed996d9e094053752664ae9b3a4156a8745bff1a/static/img/logo.png
--------------------------------------------------------------------------------
/static/js/cowyo.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var oulipo = false;
3 |
4 | $(window).load(function() {
5 | // Returns a function, that, as long as it continues to be invoked, will not
6 | // be triggered. The function will be called after it stops being called for
7 | // N milliseconds. If `immediate` is passed, trigger the function on the
8 | // leading edge, instead of the trailing.
9 | function debounce(func, wait, immediate) {
10 | var timeout;
11 | return function() {
12 | $('#saveEditButton').removeClass()
13 | $('#saveEditButton').text("Editing");
14 | var context = this,
15 | args = arguments;
16 | var later = function() {
17 | timeout = null;
18 | if (!immediate) func.apply(context, args);
19 | };
20 | var callNow = immediate && !timeout;
21 | clearTimeout(timeout);
22 | timeout = setTimeout(later, wait);
23 | if (callNow) func.apply(context, args);
24 | };
25 | };
26 |
27 | // This will apply the debounce effect on the keyup event
28 | // And it only fires 500ms or half a second after the user stopped typing
29 | var prevText = $('#userInput').val();
30 | console.log("debounce: " + window.cowyo.debounceMS)
31 | $('#userInput').on('keyup', debounce(function() {
32 | if (prevText == $('#userInput').val()) {
33 | return // no changes
34 | }
35 | prevText = $('#userInput').val();
36 |
37 | if (oulipo) {
38 | $('#userInput').val($('#userInput').val().replace(/e/g,""));
39 | }
40 | $('#saveEditButton').removeClass()
41 | $('#saveEditButton').text("Saving")
42 | upload();
43 | }, window.cowyo.debounceMS));
44 |
45 | var latestUpload = null, needAnother = false;
46 | function upload() {
47 | // Prevent concurrent uploads
48 | if (latestUpload != null) {
49 | needAnother = true;
50 | return
51 | }
52 | latestUpload = $.ajax({
53 | type: 'POST',
54 | url: '/update',
55 | data: JSON.stringify({
56 | new_text: $('#userInput').val(),
57 | page: window.cowyo.pageName,
58 | fetched_at: window.lastFetch,
59 | }),
60 | success: function(data) {
61 | latestUpload = null;
62 |
63 | $('#saveEditButton').removeClass()
64 | if (data.success == true) {
65 | $('#saveEditButton').addClass("success");
66 | window.lastFetch = data.unix_time;
67 |
68 | if (needAnother) {
69 | upload();
70 | };
71 | } else {
72 | $('#saveEditButton').addClass("failure");
73 | }
74 | $('#saveEditButton').text(data.message);
75 | needAnother = false;
76 | },
77 | error: function(xhr, error) {
78 | latestUpload = null;
79 | needAnother = false;
80 | $('#saveEditButton').removeClass()
81 | $('#saveEditButton').addClass("failure");
82 | $('#saveEditButton').text(error);
83 | },
84 | contentType: "application/json",
85 | dataType: 'json'
86 | });
87 | }
88 |
89 | function primeForSelfDestruct() {
90 | $.ajax({
91 | type: 'POST',
92 | url: '/prime',
93 | data: JSON.stringify({
94 | page: window.cowyo.pageName,
95 | }),
96 | success: function(data) {
97 | $('#saveEditButton').removeClass()
98 | if (data.success == true) {
99 | $('#saveEditButton').addClass("success");
100 | } else {
101 | $('#saveEditButton').addClass("failure");
102 | }
103 | $('#saveEditButton').text(data.message);
104 | },
105 | error: function(xhr, error) {
106 | $('#saveEditButton').removeClass()
107 | $('#saveEditButton').addClass("failure");
108 | $('#saveEditButton').text(error);
109 | },
110 | contentType: "application/json",
111 | dataType: 'json'
112 | });
113 | }
114 |
115 | function lockPage(passphrase) {
116 | $.ajax({
117 | type: 'POST',
118 | url: '/lock',
119 | data: JSON.stringify({
120 | page: window.cowyo.pageName,
121 | passphrase: passphrase
122 | }),
123 | success: function(data) {
124 | $('#saveEditButton').removeClass()
125 | if (data.success == true) {
126 | $('#saveEditButton').addClass("success");
127 | } else {
128 | $('#saveEditButton').addClass("failure");
129 | }
130 | $('#saveEditButton').text(data.message);
131 | if (data.success == true && $('#lockPage').text() == "Lock") {
132 | window.location = "/" + window.cowyo.pageName + "/view";
133 | }
134 | if (data.success == true && $('#lockPage').text() == "Unlock") {
135 | window.location = "/" + window.cowyo.pageName + "/edit";
136 | }
137 | },
138 | error: function(xhr, error) {
139 | $('#saveEditButton').removeClass()
140 | $('#saveEditButton').addClass("failure");
141 | $('#saveEditButton').text(error);
142 | },
143 | contentType: "application/json",
144 | dataType: 'json'
145 | });
146 | }
147 |
148 | function publishPage() {
149 | $.ajax({
150 | type: 'POST',
151 | url: '/publish',
152 | data: JSON.stringify({
153 | page: window.cowyo.pageName,
154 | publish: $('#publishPage').text() == "Publish"
155 | }),
156 | success: function(data) {
157 | $('#saveEditButton').removeClass()
158 | if (data.success == true) {
159 | $('#saveEditButton').addClass("success");
160 | } else {
161 | $('#saveEditButton').addClass("failure");
162 | }
163 | $('#saveEditButton').text(data.message);
164 | if (data.message == "Unpublished") {
165 | $('#publishPage').text("Publish");
166 | } else {
167 | $('#publishPage').text("Unpublish");
168 | }
169 | },
170 | error: function(xhr, error) {
171 | $('#saveEditButton').removeClass()
172 | $('#saveEditButton').addClass("failure");
173 | $('#saveEditButton').text(error);
174 | },
175 | contentType: "application/json",
176 | dataType: 'json'
177 | });
178 | }
179 |
180 | function encryptPage(passphrase) {
181 | $.ajax({
182 | type: 'POST',
183 | url: '/encrypt',
184 | data: JSON.stringify({
185 | page: window.cowyo.pageName,
186 | passphrase: passphrase
187 | }),
188 | success: function(data) {
189 | $('#saveEditButton').removeClass()
190 | if (data.success == true) {
191 | $('#saveEditButton').addClass("success");
192 | } else {
193 | $('#saveEditButton').addClass("failure");
194 | }
195 | $('#saveEditButton').text(data.message);
196 |
197 | if (data.success == true && $('#encryptPage').text() == "Encrypt") {
198 | window.location = "/" + window.cowyo.pageName + "/view";
199 | }
200 | if (data.success == true && $('#encryptPage').text() == "Decrypt") {
201 | window.location = "/" + window.cowyo.pageName + "/edit";
202 | }
203 | },
204 | error: function(xhr, error) {
205 | $('#saveEditButton').removeClass()
206 | $('#saveEditButton').addClass("failure");
207 | $('#saveEditButton').text(error);
208 | },
209 | contentType: "application/json",
210 | dataType: 'json'
211 | });
212 | }
213 |
214 | function clearOld() {
215 | $.ajax({
216 | type: 'DELETE',
217 | url: '/oldlist',
218 | data: JSON.stringify({
219 | page: window.cowyo.pageName
220 | }),
221 | success: function(data) {
222 | $('#saveEditButton').removeClass()
223 | if (data.success == true) {
224 | $('#saveEditButton').addClass("success");
225 | } else {
226 | $('#saveEditButton').addClass("failure");
227 | }
228 | $('#saveEditButton').text(data.message);
229 | if (data.success == true) {
230 | window.location = "/" + window.cowyo.pageName + "/list";
231 | }
232 | },
233 | error: function(xhr, error) {
234 | $('#saveEditButton').removeClass();
235 | $('#saveEditButton').addClass("failure");
236 | $('#saveEditButton').text(error);
237 | },
238 | contentType: "application/json",
239 | dataType: 'json'
240 | });
241 | }
242 |
243 | $("#encryptPage").click(function(e) {
244 | e.preventDefault();
245 | var passphrase = prompt("Please enter a passphrase. Note: Encrypting will remove all previous history.", "");
246 | if (passphrase != null) {
247 | encryptPage(passphrase);
248 | }
249 | });
250 |
251 | $("#erasePage").click(function(e) {
252 | e.preventDefault();
253 | var r = confirm("Are you sure you want to erase?");
254 | if (r == true) {
255 | window.location = "/" + window.cowyo.pageName + "/erase";
256 | } else {
257 | x = "You pressed Cancel!";
258 | }
259 | });
260 |
261 | $("#selfDestructPage").click(function(e) {
262 | e.preventDefault();
263 | var r = confirm("This will erase the page the next time it is opened, are you sure you want to do that?");
264 | if (r == true) {
265 | primeForSelfDestruct();
266 | } else {
267 | x = "You pressed Cancel!";
268 | }
269 | });
270 |
271 | $("#lockPage").click(function(e) {
272 | e.preventDefault();
273 | var passphrase = prompt("Please enter a passphrase to lock", "");
274 | if (passphrase != null) {
275 | if ($('#lockPage').text() == "Lock") {
276 | $('#saveEditButton').removeClass();
277 | $("#saveEditButton").text("Locking");
278 | } else {
279 | $('#saveEditButton').removeClass();
280 | $("#saveEditButton").text("Unlocking");
281 | }
282 | lockPage(passphrase);
283 | // POST encrypt page
284 | // reload page
285 | }
286 | });
287 |
288 | $("#publishPage").click(function(e) {
289 | e.preventDefault();
290 | var message = " This will add your page to the sitemap.xml so it will be indexed by search engines.";
291 | if ($('#publishPage').text() == "Unpublish") {
292 | message = "";
293 | }
294 | var confirmed = confirm("Are you sure?" + message);
295 | if (confirmed == true) {
296 | if ($('#publishPage').text() == "Unpublish") {
297 | $('#saveEditButton').removeClass();
298 | $("#saveEditButton").text("Unpublishing");
299 | } else {
300 | $('#saveEditButton').removeClass();
301 | $("#saveEditButton").text("Publishing");
302 | }
303 | publishPage();
304 | }
305 | });
306 |
307 | $("#clearOld").click(function(e) {
308 | e.preventDefault();
309 | var r = confirm("This will erase all cleared list items, are you sure you want to do that? (Versions will stay in history).");
310 | if (r == true) {
311 | clearOld()
312 | } else {
313 | x = "You pressed Cancel!";
314 | }
315 | });
316 |
317 | $("textarea").keydown(function(e) {
318 | if(e.keyCode === 9) { // tab was pressed
319 | // get caret position/selection
320 | var start = this.selectionStart;
321 | var end = this.selectionEnd;
322 |
323 | var $this = $(this);
324 | var value = $this.val();
325 |
326 | // set textarea value to: text before caret + tab + text after caret
327 | $this.val(value.substring(0, start)
328 | + "\t"
329 | + value.substring(end));
330 |
331 | // put caret at right position again (add one for the tab)
332 | this.selectionStart = this.selectionEnd = start + 1;
333 |
334 | // prevent the focus lose
335 | e.preventDefault();
336 | }
337 | });
338 |
339 | $('.deletable').click(function(event) {
340 | event.preventDefault();
341 | var lineNum = $(this).attr('id');
342 |
343 | $.ajax({
344 | url: "/listitem" + '?' + $.param({
345 | "lineNum": lineNum,
346 | "page": window.cowyo.pageName
347 | }),
348 | type: 'DELETE',
349 | success: function() {
350 | window.location.reload(true);
351 | }
352 | });
353 | event.target.classList.add('deleting');
354 | });
355 | });
356 |
357 | // TODO: Avoid uploading the same thing twice (check if it's already present while allowing failed uploads to be overwritten?)
358 | function onUploadFinished(file) {
359 | this.removeFile(file);
360 | var cursorPos = $('#userInput').prop('selectionStart');
361 | var cursorEnd = $('#userInput').prop('selectionEnd');
362 | var v = $('#userInput').val();
363 | var textBefore = v.substring(0, cursorPos);
364 | var textAfter = v.substring(cursorPos, v.length);
365 | var message = 'uploaded file';
366 | if (cursorEnd > cursorPos) {
367 | message = v.substring(cursorPos, cursorEnd);
368 | textAfter = v.substring(cursorEnd, v.length);
369 | }
370 | var prefix = '';
371 | if (file.type.startsWith("image")) {
372 | prefix = '!';
373 | }
374 | var extraText = prefix+'['+file.xhr.getResponseHeader("Location").split('filename=')[1]+'](' +
375 | file.xhr.getResponseHeader("Location") +
376 | ')';
377 |
378 | $('#userInput').val(
379 | textBefore +
380 | extraText +
381 | textAfter
382 | );
383 |
384 | // Select the newly-inserted link
385 | $('#userInput').prop('selectionStart', cursorPos);
386 | $('#userInput').prop('selectionEnd', cursorPos + extraText.length);
387 | $('#userInput').trigger('keyup'); // trigger a save
388 | }
389 |
--------------------------------------------------------------------------------
/static/text/adjectives.old:
--------------------------------------------------------------------------------
1 | lulling, vile, foul, cheerful, messy, dreadful, uneven, stinky, young, sparkling, sweltering, verdant, hideous, friendly, blistering, rambunctious, carefree, fat, sloppy, gloomy, awful, anemic, minute, stiff, benevolent, ceaseless, large, quick, round, glassy, rusty, scarce, odd, shining, even, dowdy, solemn, scorching, brief, rotten, new, plush, cozy, meandering, apologetic, nimble, busy, strong, great, brilliant, piercing, creepy, miniature, narrow, whimsical, fantastic, cowardly, disgusting, marvelous, snug, stern, stingy, angry, spiky, cheeky, gorgeous, mysterious, flat, clever, charming, dismal, meek, somber, sour, thin, beautiful, stubborn, crazy, challenging, gaunt, salty, indifferent, huge, daring, awkward, picturesque, copious, glowing, truthful, rude, petite, cranky, ornery, brazen, modest, purring, filthy, rotund, short, splendid, hasty, deafening, crawling, furious, tall, mute, ghastly, still, arrogant, crabby, haughty, curly, voiceless, hot, courageous, late, microscopic, vast, stifling, good, disrespectful, bashful, moaning, towering, adventurous, idyllic, careless, shocking, erratic, heavy, square, hard, jagged, gullible, hushed, revolting, content, thrifty, sluggish, eternal, greedy, pleasant, small, demanding, greasy, enormous, hostile, terrible, chilly, speedy, massive, loud, puny, striking, clumsy, soaring, brave, fast, delicious, effortless, bland, thick, little, ancient, silent, wonderful, stuffed, grimy, bitter, muggy, shallow, ridiculous, absent-minded, fuzzy, peculiar, mangy, wide, kind, squeaky, screeching, silly, squealing, spoiled, gigantic, happy, steep, ingenious, modern, juicy, gentle, medium, brawny, curved, lumpy, afraid, amusing, thundering, cooing, oppressive, swollen, grave, sturdy, average, proud, rancid, absurd, entertaining, annoyed, fussy, precise, subtle, gilded, slow, delinquent, nervous, hopeful, rich, adequate, shrill, plump, freezing, nasty, endless, lavish, worried, courteous, bulky, fair, diminutive, groggy, miserable, horrid, crooked, monstrous, superb, contrary, lazy, fidgety, menacing, swift, stale, quarrelsome, quiet, askew, tough, simple, sweet, hardworking, frosty, whispering, famished, crispy, caring, capable, tiny, immense, startled, lovely, high-pitched, tasteless, decrepit, tense, lousy, straight, excited, ugly, stunning, parched, wild, ripe, lonely, optimistic, obnoxious, cavernous, different, harsh, creaky, grand, difficult, temporary, eccentric, muffled, alert, delicate, timid, infamous, enchanting, anxious, humble, edgy, severe, repulsive, desolate, sleepy, slimy, irritable, vigilant, generous, rapid, old-fashioned, hilly, easy, righteous, joyful, surprised, starving, big, early, compassionate, moody, perpetual, dishonest, serious, foolish, soft, old, scared, mighty, trendy, curious, hissing, savage, dense, steaming, broad, slick, creative, icy, adorable, slight, terrified, intense, noisy, cautious, sizzling, blithe, fluttering, faint, delighted, smelly, lively, frightened, gauzy, long, strict, bored, calm, melodic, spicy, relaxed, triangular, dull, wise, dangerous, smooth, cruel, creeping, dawdling, intimidating, exhausted, deep, tasty, obtuse, graceful, tranquil, raspy, selfish, sullen, malicious, ecstatic, wrinkly, opulent, polite, fetid, husky, prudent, skinny, tricky, impatient, loyal, fresh
2 |
--------------------------------------------------------------------------------
/static/text/animals:
--------------------------------------------------------------------------------
1 | fawn, peacock, fox terrier, civet, musk deer, seastar, pigeon, bull, bumblebee, crocodile, flying squirrel, elephant, leopard seal, baboon, porcupine, wolverine, spider monkey, vampire bat, sparrow, manatee, possum, swallow, wildcat, bandicoot, labradoodle, dragonfly, tarsier, snowy owl, chameleon, boykin, puffin, bison, llama, kitten, stinkbug, macaw, parrot, leopard cat, prawn, panther, dogfish, fennec, frigatebird, nurse shark, turkey, cockatoo, neanderthal, crow, gopher, reindeer, earwig , anaconda, panda, ant, silver fox, collared peccary, puppy, common buzzard, moose, binturong, wildebeest, lovebird, ferret, persian, marine toad, woolly mammoth, dalmatian, bird, umbrellabird, kingfisher, kangaroo, stallion, russian blue, ostrich, owl, tawny owl, affenpinscher, caiman, elephant seal, octopus, meerkat, whale shark, buck, donkey, red wolf, mountain lion, labrador retriever, quetzal, chamois, sponge, hamster, orangutan, sea urchin, uakari, doberman, dormouse, saint bernard, bull shark, ocelot, sparrow, spitz, stoat, snapping turtle, dragonfly, cougar, alligator, walrus, glass lizard, malayan tiger, frog, tiger, armadillo, chinchilla, crab, squid, calf, shrew, dolphin, royal penguin, dingo, turtle, yellow-eyed penguin, chimpanzee, armadillo, boa constrictor, rabbit, basking, coyote, chinook, osprey, sea lion, fly, sperm whale, patas monkey, tiffany, mountain goat, dodo, worm, cat, warthog, peccary, shark, pony, monkey, swan, whippet, beagle, cougar, anteater, quail, liger, cheetah, woodpecker, egret, eagle, moose, warthog, honey bee, snail, stag beetle, budgie, molly, magpie, rhinoceros, elephant, kudu, wombat, tree frog, goat, lamb, tropicbird, human, hog, tang, pool frog, lemur, ox, dog, lizard, echidna, great dane, wallaby, hawk, dove, jellyfish, sloth, macaque, starfish, sun bear, guppy, welsh corgi, deer, impala, porpoise, gazelle, bichon, seal, wolf, zebra shark, mole, narwhal, hedgehog, sheep, horse, bluetick, colt, spadefoot toad, wildebeest, piranha, basenji, mallard, bull mastiff, bear, siberian husky, bird, badger, red panda, hammerhead, rock hyrax, kangaroo, marsh frog, mule, weasel, dogfish, dachsbracke, forest elephant, oyster, bat, python, coati, platypus, salamander, cat, caterpillar, giraffe, snake, kid, falcon, robin, guinea fowl, tern, sea lion, dingo, bolognese, drake, goose, rat, gentoo penguin, iguana, quail, mouse, horseshoe crab, roebuck, cattle dog, fish, poodle, frog, wolverine, chinchilla, bobcat, grey seal, hermit crab, carolina, shepherd, gila monster, snail, mandrill, leopard, frilled lizard, echidna, rabbit, bison, barracuda, foal, ass, eagle, octopus, avocet, siamese, dodo, yorkie, cockroach, wallaroo, tiger, woodlouse, glow worm, fossa, buffalo, zorse, albatross, indri, seahorse, lemur, louse, ostrich, humpback whale, millipede, fin whale, joey, pinscher, dachshund, proboscis monkey, pelican, chihuahua, dogo, indian rhinoceros, wasp, siberian, raccoon dog, yak, stingray, jack russel, water vole, foxhound, sheep, stork, horse, monkey, woolly monkey, waterbuck, dunker, cuscus, ibis, giraffe, aardvark, hummingbird, grizzly bear, otter, pike, minke whale, pika, stickbug, pelican, dugong, bongo, lemming, shrimp, piglet, sabre-toothed tiger, gemsbok, tiger shark, tuatara, rottweiler, elephant shrew, ewe, coati, cichlid, akita, gharial, thorny devil, duck, macaroni penguin, steer, setter, pufferfish, donkey, mink, macaw, wolfhound, white tiger, ram, ant, rat, marten, galapagos tortoise, crab, horn shark, blue whale, koala, starfish, partridge, sea squirt, fire-bellied toad, chipmunk, ibex, maltese, clumber, butterfly, manta ray, flamingo, opossum, parrot, mastiff, water buffalo, okapi, salmon, tapir, adelie, killer whale, lynx, basilisk, indian elephant, oyster, manta ray, prairie dog, chipmunk, locust, dog, cottontop, hyena, spectacled bear, oriole, cobra, pug, monitor, mandrill, antelope, chinstrap, zebra, chicken, mule, seal, goat, little penguin, gull, tasmanian devil, caterpillar, tamarin, wrasse, woodchuck, otter, penguin, porcupine, killer whale, bear, ferret, dusky, nightingale, slow worm, bat, jaguar, humboldt, ermine, saola, emu, lobster, weasel, nightingale, hound, bombay, platypus, electric eel, asian elephant, sea otter, uguisu, scorpion, fox, jerboa, bengal tiger, zebu, lion, zonkey, ragdoll, caracal, bee, kiwi, puma, common loon, jackal, malamute, mayfly, baboon, terrier, jellyfish, vicuna, penguin, desert tortoise, muskrat, water dragon, zebra, malayan civet, burmese, orangutan, himalayan, pond skater, howler monkey, newt, border collie, cow, bearded dragon, fish, barn owl, puffin, chin, anteater, beaver, canary, hamster, sloth, collie, heron, sea dragon, gopher, magpie, king crab, flounder, opossum, pademelon, capybara, boar, leaf-tailed gecko, turkey, clown fish, musk-ox, bulldog, pronghorn, hercules beetle, reindeer, llama, pygmy, eskimo dog, kinkajou, komodo dragon, cuttlefish, cub, bloodhound, squirrel, gander, moorhen, emu, brown bear, javanese, birman, harrier, tortoise, antelope, gnu, kingfisher, wasp, olm, havanese, canaan, lizard, indochinese tiger, ocelot, mist, hare, discus, cony, orca, rooster, ground hog, silver dollar, peacock, akbash, somali, beaver, maine coon, mouse, eland, squirrel, serval, chimpanzee, snowshoe, toucan, catfish, lynx, coyote, bunny, retriever, fur seal, cow, balinese, vulture, coral, leopard, raccoon, polar bear, okapi, kakapo, whale, sand lizard, bonobo, moray, gila monster, cormorant, bracke, camel, markhor, rockhopper, neapolitan, black bear, roseate spoonbill, woodpecker, mountain lion, crested penguin, hippopotamus, puma, camel, alligator, guinea pig, heron, siberian tiger, river dolphin, axolotl, argentino, human, mongoose, drever, quokka, common frog, elk, wombat, spider monkey, civet, sting ray, panther, gar, lionfish, snake, crane, newt, raven, tortoise, fire ant, chicadee, common toad, pig, manatee, centipede, numbat, river turtle, falcon, angelfish, chamois, rhinoceros, shark, flamingo, pheasant, ladybird, grasshopper, greyhound, lemming, pig, marmoset, eel, yorkiepoo, tiger salamander, mosquito, shih tzu, quoll, chick, guanaco, walrus, badger, ainu, squid, pekingese, gerbil, duck, rattlesnake, tapir, lobster, catfish, mustang, wallaby, mongrel, butterfly, booby, bush elephant, fox, rattlesnake, cockroach, tadpole, lark, ape, pied tamarin, mare, tetra, squirrel monkey, elephant seal, dhole, cesky, raccoon, newfoundland, marmoset, stag, bullfrog, black bear, crocodile, lion, barb, wolf, vervet monkey, beetle, polar bear, pointer, grizzly bear, meerkat, owl, reptile, fousek, gibbon, king penguin, budgerigar, swan, hartebeest, cassowary, borneo elephant, oryx, alpaca, gerbil, chameleon, galapagos penguin, vulture, barracuda, insect, fishing cat, hen, giant clam, hare, polecat, fly, chow chow, yak, seahorse, spider, eel, burro, brown bear, boxer dog, crane, bandicoot, hedgehog, dromedary, goose, budgerigar, dolphin, kelpie dog, highland cattle, dormouse, duckbill, springbok, mongoose, bobcat, water buffalo, gecko, hornet, iguana, wild boar, koala, guinea pig, marmot, skink, deer, filly, barnacle, tree toad, leopard tortoise, appenzeller, doe, gecko, mole, mynah bird, mau, gilla monster, french bulldog, termite, salamander, parakeet, finch, horned frog, hippopotamus, hummingbird, cheetah, albatross, jaguar, toad, hyena, gorilla, skunk, impala, sea slug, scorpion fish, jackal, skunk, grouse, sea turtle, moth, caribou, dugong, bighorn sheep, ibizan hound, gorilla, puffer fish, chicken, komodo dragon, buffalo
2 |
--------------------------------------------------------------------------------
/static/text/howmany.py:
--------------------------------------------------------------------------------
1 | adjectives = {}
2 | with open('adjectives','r') as f:
3 | for line in f:
4 | word = line.strip().lower()
5 | if word[0] not in adjectives:
6 | adjectives[word[0]] = []
7 | adjectives[word[0]].append(word)
8 |
9 | print(len(adjectives.keys()))
10 |
11 | animals = {}
12 | for aword in open('animals','r').read().split(','):
13 | word = aword.strip().lower()
14 | if word[0] not in animals:
15 | animals[word[0]] = []
16 | animals[word[0]].append(word)
17 |
18 | print(len(animals))
19 |
20 | i = 0
21 | for key in adjectives.keys():
22 | if key in animals and key in adjectives:
23 | i = i + len(adjectives[key])*len(animals[key])
24 |
25 | print(i)
26 |
--------------------------------------------------------------------------------
/static/text/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
3 | Allow: /Help/view
4 | Allow: /sitemap.xml
5 |
--------------------------------------------------------------------------------
/static/text/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | https://awwkoala.com/Help/view
5 | 2016-03-18
6 | monthly
7 | 1.0
8 |
9 |
10 |
--------------------------------------------------------------------------------
/templates/index.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ if and .CustomCSS .ReadPage }}
25 |
26 | {{ else }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{ end }}
38 |
39 | {{ .Page }}
40 |
41 |
49 |
50 |
51 |
61 |
62 |
63 | {{ if .ReadPage }}
64 |
65 | {{ else }}
66 |
131 | {{ end }}
132 |
133 |
134 | {{ if .EditPage }}
135 |
136 |
137 |
138 |
147 |
148 |
161 |
162 | {{ end }}
163 |
164 |
165 | {{ if .DontKnowPage }}
166 |
167 |
168 | {{ .Route }} not understood!
169 |
170 |
171 | {{ end }}
172 |
173 | {{ if .ViewPage }}
174 | {{ .RenderedPage }}
175 | {{ end }}
176 |
177 | {{ if .ReadPage }}
178 | {{ .RenderedPage }}
179 | {{ end }}
180 |
181 | {{ if .HistoryPage }}
182 |
History
183 |
184 | {{range $i, $e := .Versions}}
185 | -
186 | View
187 |
188 | List
189 |
190 | Raw
191 |
192 | {{index $.VersionsText $i}} ({{if lt (index $.VersionsChangeSums $i) 0}}{{else}}+{{end}}{{index $.VersionsChangeSums $i}})
193 | {{end}}
194 |
195 | {{ end }}
196 |
197 | {{ if .ListPage }}
198 | {{ range $index, $element := .ListItems }}
199 | {{ $element }}
200 | {{ end }}
201 | {{ end }}
202 |
203 | {{ if .DirectoryPage }}
204 |
205 | {{ $upload := .UploadPage }}
206 |
207 | Document |
208 | Current size |
209 | {{ if not $upload }}
210 | Num Edits |
211 | {{ end }}
212 | Last Edited |
213 |
214 | {{range .DirectoryEntries}}
215 |
216 |
217 | {{ if $upload }}
218 | {{ sniffContentType .Name }}
219 | {{ else }}
220 | {{ .Name }}
221 | {{ end }}
222 | |
223 | {{.Size}} |
224 | {{ if not $upload }}
225 | {{.Numchanges}} |
226 | {{ end }}
227 | {{.ModTime.Format "Mon Jan 2 15:04:05 MST 2006" }} |
228 |
229 | {{ end }}
230 |
231 | {{ end }}
232 |
233 | {{ if .ChildPageNames }}
234 |
235 | See also
236 |
237 | {{ range .ChildPageNames }}
238 | - {{ . }}
239 | {{ end }}
240 |
241 |
242 | {{ end }}
243 |
244 |
245 |
246 |
247 |
--------------------------------------------------------------------------------