├── .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 | linkcrawler 6 |
7 | Build Status Version

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 | ![Editing](http://i.imgur.com/vEs2U8z.gif) 90 | 91 | ### History 92 | 93 | You can easily see previous versions of your documents. 94 | 95 | ![History](http://i.imgur.com/CxhRkyo.gif) 96 | 97 | ### Lists 98 | 99 | You can easily make lists and check them off. 100 | 101 | ![Lists](http://i.imgur.com/7xbauy8.gif) 102 | 103 | ### Locking 104 | 105 | Locking prevents other users from editing your pages without a passphrase. 106 | 107 | ![Locking](http://i.imgur.com/xwUFV8b.gif) 108 | 109 | ### Encryption 110 | 111 | Encryption is performed using AES-256. 112 | 113 | ![Encryption](http://i.imgur.com/rWoqoLB.gif) 114 | 115 | ### Self-destructing pages 116 | 117 | Just like in mission impossible. 118 | 119 | ![Self-destructing](http://i.imgur.com/upMxFQh.gif) 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 |
    155 | 160 |
    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 | 208 | 209 | {{ if not $upload }} 210 | 211 | {{ end }} 212 | 213 | 214 | {{range .DirectoryEntries}} 215 | 216 | 223 | 224 | {{ if not $upload }} 225 | 226 | {{ end }} 227 | 228 | 229 | {{ end }} 230 |
    DocumentCurrent sizeNum EditsLast Edited
    217 | {{ if $upload }} 218 | {{ sniffContentType .Name }} 219 | {{ else }} 220 | {{ .Name }} 221 | {{ end }} 222 | {{.Size}}{{.Numchanges}}{{.ModTime.Format "Mon Jan 2 15:04:05 MST 2006" }}
    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 | --------------------------------------------------------------------------------