mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
Compare commits
277 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ed996d9e09 | ||
![]() |
a58f4ca346 | ||
![]() |
a318863eca | ||
![]() |
d8d3200491 | ||
![]() |
f414ccc2f3 | ||
![]() |
0a9b069152 | ||
![]() |
4954276644 | ||
![]() |
156722d23e | ||
![]() |
9accd685c0 | ||
![]() |
126b13fcea | ||
![]() |
5a0587c3ce | ||
![]() |
e5a1a88a02 | ||
![]() |
d43cc80b53 | ||
![]() |
ff84acd9d7 | ||
![]() |
1e46c40cc6 | ||
![]() |
9e928f6b74 | ||
![]() |
3b91b699e3 | ||
![]() |
f567f86bab | ||
![]() |
0e93250cc9 | ||
![]() |
82d5ac908d | ||
![]() |
7a61b16e7a | ||
![]() |
f86dc1095a | ||
![]() |
a976007642 | ||
![]() |
180a28cda6 | ||
![]() |
08bbce1298 | ||
![]() |
e53123d8fa | ||
![]() |
d48d1458a5 | ||
![]() |
d9b8bfc95d | ||
![]() |
bc1a9ee86b | ||
![]() |
f4f5042245 | ||
![]() |
d095915f83 | ||
![]() |
c2cd54a12d | ||
![]() |
2e80633cd4 | ||
![]() |
9cf0d0e129 | ||
![]() |
ede4d1fba3 | ||
![]() |
ff5c100cf8 | ||
![]() |
465e9c8e93 | ||
![]() |
0d984da5b2 | ||
![]() |
8944170646 | ||
![]() |
f21c89f7bd | ||
![]() |
91178f4f29 | ||
![]() |
8bc1123f1d | ||
![]() |
5d0523117b | ||
![]() |
6e1d64b40e | ||
![]() |
f9b8713404 | ||
![]() |
fc3030339f | ||
![]() |
df406ec71b | ||
![]() |
2a43ebdb53 | ||
![]() |
d9e622fa9d | ||
![]() |
446ba00fe9 | ||
![]() |
f729aece51 | ||
![]() |
e5dfacb4bb | ||
![]() |
2373e339d0 | ||
![]() |
c64e54a991 | ||
![]() |
f350d0bbad | ||
![]() |
308244cfa4 | ||
![]() |
50624e75e9 | ||
![]() |
efd4cfb0d1 | ||
![]() |
4ebf9e02ef | ||
![]() |
c1a78c2006 | ||
![]() |
8a4ba13506 | ||
![]() |
c78dec28eb | ||
![]() |
0f8e572d43 | ||
![]() |
1720711029 | ||
![]() |
3c53386e08 | ||
![]() |
9d4fcae7b2 | ||
![]() |
db48f1291b | ||
![]() |
131a54a682 | ||
![]() |
bb475bd924 | ||
![]() |
dfd9aea863 | ||
![]() |
9982fb5175 | ||
![]() |
f5f0bdb3bb | ||
![]() |
d65eccbe9d | ||
![]() |
1a3e891dfd | ||
![]() |
917d32f8a5 | ||
![]() |
c207077877 | ||
![]() |
5e4a317b10 | ||
![]() |
da6d6e5d43 | ||
![]() |
d0bc74ec55 | ||
![]() |
b42750073c | ||
![]() |
e1934b9797 | ||
![]() |
0badf719e0 | ||
![]() |
a0dcc9652f | ||
![]() |
fb6405ba1b | ||
![]() |
b18f40e336 | ||
![]() |
fa66472128 | ||
![]() |
526688c7e3 | ||
![]() |
31153063f2 | ||
![]() |
b4638476cc | ||
![]() |
ff420fb81d | ||
![]() |
dec21a80c2 | ||
![]() |
f998015f0c | ||
![]() |
fea5ef4647 | ||
![]() |
ecf1d2ab10 | ||
![]() |
765c5788b3 | ||
![]() |
76ec1c1acb | ||
![]() |
89d58f5a22 | ||
![]() |
2f1c0e3cd2 | ||
![]() |
10d45b0c76 | ||
![]() |
038a895772 | ||
![]() |
ff2920965d | ||
![]() |
a36f8e318e | ||
![]() |
c802fa06be | ||
![]() |
81f6b2d263 | ||
![]() |
5803cbdc3f | ||
![]() |
5a82e77738 | ||
![]() |
55368b1c1a | ||
![]() |
7cc1eccb83 | ||
![]() |
45436a3762 | ||
![]() |
684ff4e692 | ||
![]() |
63bdbb5824 | ||
![]() |
85877602e0 | ||
![]() |
6eacf90e18 | ||
![]() |
4a916984e6 | ||
![]() |
840fbf73ec | ||
![]() |
a2288cf129 | ||
![]() |
8ccd40c105 | ||
![]() |
1ef24a0347 | ||
![]() |
8b3e7b0605 | ||
![]() |
de03d2b547 | ||
![]() |
d481145c5f | ||
![]() |
2040dbaaa5 | ||
![]() |
5ae5c91945 | ||
![]() |
0ef75a919c | ||
![]() |
da30d15eb1 | ||
![]() |
9d22d5d41d | ||
![]() |
c35aeca859 | ||
![]() |
e52d0097a9 | ||
![]() |
b2a01d0a6d | ||
![]() |
026dd7c647 | ||
![]() |
107a0dc32b | ||
![]() |
683bcdea1c | ||
![]() |
e33ddcfddb | ||
![]() |
6baa87daa4 | ||
![]() |
912bf83c59 | ||
![]() |
ecba3aa0ea | ||
![]() |
b77c32d646 | ||
![]() |
bef20f3366 | ||
![]() |
21047443d6 | ||
![]() |
b83e3fd52e | ||
![]() |
8474b79cf1 | ||
![]() |
b3b5f31575 | ||
![]() |
90a8ea1706 | ||
![]() |
6d81fe5b19 | ||
![]() |
bb56db31f7 | ||
![]() |
b286fae089 | ||
![]() |
ce2deee49f | ||
![]() |
673e38a1c8 | ||
![]() |
2246747698 | ||
![]() |
dad4492c17 | ||
![]() |
9d87359c62 | ||
![]() |
76b054122a | ||
![]() |
def0f9d669 | ||
![]() |
4821e7ae62 | ||
![]() |
aa1b7a1afb | ||
![]() |
e10ff91f10 | ||
![]() |
bb9d95620c | ||
![]() |
c0b6365440 | ||
![]() |
89d0f828b0 | ||
![]() |
0b1c74695b | ||
![]() |
d3101da4ac | ||
![]() |
d69e37d1bc | ||
![]() |
57e28b85cd | ||
![]() |
9dbb83e8af | ||
![]() |
228b324149 | ||
![]() |
b7fc420f7e | ||
![]() |
294d4611d2 | ||
![]() |
9b5376af30 | ||
![]() |
40c5d8bee4 | ||
![]() |
2a7e98a8e2 | ||
![]() |
956cef54df | ||
![]() |
2101b706d3 | ||
![]() |
68ecbdd595 | ||
![]() |
ceaef5a1ae | ||
![]() |
9571592de0 | ||
![]() |
7d50c644e5 | ||
![]() |
4af9b87519 | ||
![]() |
23536ad927 | ||
![]() |
86c86779dd | ||
![]() |
6e1c85e205 | ||
![]() |
9248892668 | ||
![]() |
8d3a301497 | ||
![]() |
a3e1d6f6d1 | ||
![]() |
8318b5afd8 | ||
![]() |
82b16ec121 | ||
![]() |
8e6b798be5 | ||
![]() |
57cda99027 | ||
![]() |
e8959d8923 | ||
![]() |
9d9918966a | ||
![]() |
05f43196c5 | ||
![]() |
c35ab4eb4a | ||
![]() |
5fab2749f9 | ||
![]() |
da2dcee3fd | ||
![]() |
e11baae47c | ||
![]() |
035cd7b6d9 | ||
![]() |
7b1ce17506 | ||
![]() |
14f563572d | ||
![]() |
5d273c037a | ||
![]() |
dbd1be938e | ||
![]() |
3f98cb7e70 | ||
![]() |
f09cf1b3e9 | ||
![]() |
c9b9032dc9 | ||
![]() |
674b39b49a | ||
![]() |
e066ad23cc | ||
![]() |
11b671423c | ||
![]() |
82c24f2413 | ||
![]() |
fa7ae60d59 | ||
![]() |
20dd8d4f20 | ||
![]() |
bd3b9c96e1 | ||
![]() |
a78468e086 | ||
![]() |
97bfc15692 | ||
![]() |
61b8d275be | ||
![]() |
3b7e35f9ef | ||
![]() |
bbf55f539f | ||
![]() |
28d70b6148 | ||
![]() |
3b0169212c | ||
![]() |
90faae9e85 | ||
![]() |
e9e513171c | ||
![]() |
bd6ed8d3c0 | ||
![]() |
c1b9b71669 | ||
![]() |
edcd2e7d09 | ||
![]() |
3fd886e625 | ||
![]() |
4709654c4c | ||
![]() |
cc04599b6f | ||
![]() |
d247846934 | ||
![]() |
79768ce9be | ||
![]() |
a16fd6f8fb | ||
![]() |
62c9e6ee1b | ||
![]() |
3a265a0979 | ||
![]() |
39ebd6c125 | ||
![]() |
bd85204eff | ||
![]() |
322294b2ac | ||
![]() |
1686409cf7 | ||
![]() |
0109a94cbb | ||
![]() |
17bf3c3a4c | ||
![]() |
cbede33317 | ||
![]() |
dbeea86d6a | ||
![]() |
accc3b5f62 | ||
![]() |
12d447ed74 | ||
![]() |
328e177b66 | ||
![]() |
38e0943df3 | ||
![]() |
c66c312fdb | ||
![]() |
c3ba228288 | ||
![]() |
3a463449fb | ||
![]() |
bca008f315 | ||
![]() |
6347038563 | ||
![]() |
a203fcc32f | ||
![]() |
538fa425b0 | ||
![]() |
b4fecbfaa2 | ||
![]() |
fb5ee8d09e | ||
![]() |
522c4c6283 | ||
![]() |
f42c0f411b | ||
![]() |
59284f2601 | ||
![]() |
d46fe9cb6a | ||
![]() |
64b3fdb0d4 | ||
![]() |
6031590d87 | ||
![]() |
4d3385b3f1 | ||
![]() |
3ba5537c1e | ||
![]() |
8a7803250f | ||
![]() |
97edbd73a8 | ||
![]() |
79422d1fb3 | ||
![]() |
0be3c91823 | ||
![]() |
04f1cc87e9 | ||
![]() |
706e4e6fdf | ||
![]() |
5e1ac31d13 | ||
![]() |
7d878a18b4 | ||
![]() |
95401434a2 | ||
![]() |
074b776984 | ||
![]() |
4ac089780e | ||
![]() |
d0791f3b2e | ||
![]() |
18d002abf4 | ||
![]() |
4276e4bec3 | ||
![]() |
eca269696f | ||
![]() |
a3a881b65e | ||
![]() |
09b09f3dbc | ||
![]() |
dba80e26c7 | ||
![]() |
7d6cb4910c |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: schollz
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -22,3 +22,8 @@ _testmain.go
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
data/*
|
||||
cowyo
|
||||
dist
|
||||
|
||||
|
8
.travis.yml
Normal file
8
.travis.yml
Normal file
@ -0,0 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -u github.com/schollz/cowyo
|
||||
- go get -u github.com/jteeuwen/go-bindata/...
|
43
Dockerfile
43
Dockerfile
@ -1,32 +1,13 @@
|
||||
# sudo docker build -t cowyo .
|
||||
# sudo docker run -it -p 8003:8003 -v `pwd`/data:/data cowyo bash
|
||||
FROM ubuntu:16.04
|
||||
FROM golang:1.12-alpine as builder
|
||||
RUN apk add --no-cache git make
|
||||
RUN go get -v github.com/jteeuwen/go-bindata/go-bindata
|
||||
WORKDIR /go/cowyo
|
||||
COPY . .
|
||||
RUN make build
|
||||
|
||||
# Get basics
|
||||
RUN apt-get update
|
||||
RUN apt-get -y upgrade
|
||||
RUN apt-get install -y golang git wget curl vim
|
||||
RUN mkdir /usr/local/work
|
||||
ENV GOPATH /usr/local/work
|
||||
|
||||
# Install cowyo
|
||||
WORKDIR "/root"
|
||||
RUN go get github.com/schollz/cowyo
|
||||
RUN git clone https://github.com/schollz/cowyo.git
|
||||
WORKDIR "/root/cowyo"
|
||||
RUN git pull
|
||||
RUN go build
|
||||
|
||||
# Setup supervisor
|
||||
RUN apt-get update && apt-get install -y supervisor
|
||||
|
||||
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Add Tini
|
||||
ENV TINI_VERSION v0.9.0
|
||||
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
|
||||
RUN chmod +x /tini
|
||||
ENTRYPOINT ["/tini", "--"]
|
||||
|
||||
# Startup
|
||||
CMD ["/usr/bin/supervisord"]
|
||||
FROM alpine:latest
|
||||
VOLUME /data
|
||||
EXPOSE 8050
|
||||
COPY --from=builder /go/cowyo/cowyo /cowyo
|
||||
ENTRYPOINT ["/cowyo"]
|
||||
CMD ["--data","/data","--allow-file-uploads","--max-upload-mb","10","--host","0.0.0.0"]
|
||||
|
4
LICENSE
4
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Zack
|
||||
Copyright (c) 2017 Zack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
101
Makefile
101
Makefile
@ -1,47 +1,54 @@
|
||||
SOURCEDIR=.
|
||||
SOURCES := $(shell find $(SOURCEDIR) -name '*.go')
|
||||
|
||||
BINARY=cowyo
|
||||
|
||||
VERSION=1.1.0
|
||||
BUILD_TIME=`date +%FT%T%z`
|
||||
BUILD=`git rev-parse HEAD`
|
||||
|
||||
LDFLAGS=-ldflags "-X main.VersionNum=${VERSION} -X main.Build=${BUILD} -X main.BuildTime=${BUILD_TIME}"
|
||||
|
||||
.DEFAULT_GOAL: $(BINARY)
|
||||
|
||||
$(BINARY): $(SOURCES)
|
||||
go get github.com/boltdb/bolt
|
||||
go get github.com/gin-gonic/contrib/sessions
|
||||
go get github.com/gin-gonic/gin
|
||||
go get github.com/gorilla/websocket
|
||||
go get github.com/microcosm-cc/bluemonday
|
||||
go get github.com/russross/blackfriday
|
||||
go get github.com/sergi/go-diff/diffmatchpatch
|
||||
go get github.com/jcelliott/lumber
|
||||
go build ${LDFLAGS} -o ${BINARY} ${SOURCES}
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
if [ -f ${BINARY} ] ; then rm ${BINARY} ; fi
|
||||
rm -rf binaries
|
||||
|
||||
.PHONY: binaries
|
||||
binaries:
|
||||
rm -rf binaries
|
||||
rm -f cowyo
|
||||
mkdir binaries
|
||||
env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o ${BINARY} ${SOURCES}
|
||||
zip -9 -r cowyo-linux-64bit.zip cowyo static/* templates/*
|
||||
rm -f cowyo
|
||||
env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -o ${BINARY}.exe ${SOURCES}
|
||||
zip -9 -r cowyo-windows-64bit.zip cowyo.exe static/* templates/*
|
||||
rm -f cowyo.exe
|
||||
env GOOS=linux GOARCH=arm go build ${LDFLAGS} -o ${BINARY} ${SOURCES}
|
||||
zip -9 -r cowyo-raspberrypi.zip cowyo static/* templates/*
|
||||
rm -f cowyo
|
||||
env GOOS=darwin GOARCH=amd64 go build ${LDFLAGS} -o ${BINARY} ${SOURCES}
|
||||
zip -9 -r cowyo-macosx-64bit.zip cowyo static/* templates/*
|
||||
rm -f cowyo
|
||||
mv *.zip binaries/
|
||||
# Make a release with
|
||||
# make -j4 release
|
||||
|
||||
VERSION=$(shell git describe)
|
||||
LDFLAGS=-ldflags "-X main.version=${VERSION}"
|
||||
|
||||
.PHONY: build
|
||||
build: server/bindata.go
|
||||
go build ${LDFLAGS}
|
||||
|
||||
STATICFILES := $(wildcard static/*)
|
||||
TEMPLATES := $(wildcard templates/*)
|
||||
server/bindata.go: $(STATICFILES) $(TEMPLATES)
|
||||
go-bindata -pkg server -tags '!debug' -o server/bindata.go static/... templates/...
|
||||
go fmt
|
||||
|
||||
server/bindata-debug.go: $(STATICFILES) $(TEMPLATES)
|
||||
go-bindata -pkg server -tags 'debug' -o server/bindata-debug.go -debug static/... templates/...
|
||||
go fmt
|
||||
|
||||
.PHONY: devel
|
||||
devel: server/bindata-debug.go
|
||||
go build -tags debug
|
||||
|
||||
.PHONY: quick
|
||||
quick: server/bindata.go
|
||||
go build
|
||||
|
||||
.PHONY: linuxarm
|
||||
linuxarm: server/bindata.go
|
||||
env GOOS=linux GOARCH=arm go build ${LDFLAGS} -o dist/cowyo_linux_arm
|
||||
#cd dist && upx --brute cowyo_linux_arm
|
||||
|
||||
.PHONY: linux32
|
||||
linux32: server/bindata.go
|
||||
env GOOS=linux GOARCH=386 go build ${LDFLAGS} -o dist/cowyo_linux_32bit
|
||||
#cd dist && upx --brute cowyo_linux_32bit
|
||||
|
||||
.PHONY: linux64
|
||||
linux64: server/bindata.go
|
||||
env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_linux_amd64
|
||||
|
||||
.PHONY: windows
|
||||
windows: server/bindata.go
|
||||
env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_windows_amd64.exe
|
||||
#cd dist && upx --brute cowyo_windows_amd64.exe
|
||||
|
||||
.PHONY: osx
|
||||
osx: server/bindata.go
|
||||
env GOOS=darwin GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_osx_amd64
|
||||
#cd dist && upx --brute cowyo_osx_amd64
|
||||
|
||||
.PHONY: release
|
||||
release: osx windows linux64 linux32 linuxarm
|
||||
|
214
README.md
214
README.md
@ -1,122 +1,140 @@
|
||||

|
||||
|
||||
# [cowyo.com](http://cowyo.com/)
|
||||
|
||||
[]() [](https://goreportcard.com/report/github.com/schollz/cowyo) [](https://gitter.im/schollz/cowyo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
This is a self-contained notepad webserver that makes sharing easy and _fast_. The most important feature here is _simplicity_. There are many other features as well including versioning, page locking, self-destructing messages, encryption, math support, syntax highlighting, command line support, content-delivery, and listifying. Read on to learn more about the features.
|
||||
|
||||
# Features
|
||||
|
||||
**Simplicity**. The philosophy here is to _just type_. To jot a note, simply load the page at [`/`](http://cowyo.com/) and just start typing. No need to press edit, the browser will already be focused on the text. No need to press save - it will automatically save when you stop writing. The URL at [`/`](http://cowyo.com/) will redirect to an easy-to-remember name that you can use to reload the page at anytime, anywhere. But, you can also use any URL you want, e.g. [`/AnythingYouWant`](http://cowyo.com/AnythingYouWant). All pages can be rendered into HTML by adding `/view`. For example, the page [`/AnythingYouWant`](http://cowyo.com/AnythingYouWant) is rendered at [`/AnythingYouWant/view`](http://cowyo.com/AnythingYouWant/view). You can write in HTML or [Markdown](https://daringfireball.net/projects/markdown/) for page rendering. To quickly link to `/view` pages, just use `[[AnythingYouWant]]`.
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
src="/static/img/logo.png"
|
||||
width="260" height="80" border="0" alt="linkcrawler">
|
||||
<br>
|
||||
<a href="https://travis-ci.org/schollz/cowyo"><img
|
||||
src="https://img.shields.io/travis/schollz/cowyo.svg?style=flat-square"
|
||||
alt="Build Status"></a> <a
|
||||
href="https://github.com/schollz/cowyo/releases/latest"><img
|
||||
src="https://img.shields.io/badge/version-2.11.0-brightgreen.svg?style=flat-square"
|
||||
alt="Version"></a> </p>
|
||||
|
||||
**Listifying**. If you are writing a list and you want to tick off things really easily, just add `/list`. For example, after editing [`/grocery`](http://cowyo.com/grocery), goto [`/grocery/list`](http://cowyo.com/grocery/list). In this page, whatever you click on will be struck through and moved to the end. This is helpful if you write a grocery list and then want to easily delete things from it.
|
||||
<p align="center">A feature-rich wiki for minimalists</a></p>
|
||||
|
||||

|
||||
*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.
|
||||
|
||||
<br>
|
||||
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).
|
||||
|
||||
**Page locking**. Pages can be locked by providing a password to prevent further editing. The whole version tree will still be available. _Note_: This is not available for list mode.
|
||||
Getting Started
|
||||
===============
|
||||
|
||||

|
||||
## Install
|
||||
|
||||
<br>
|
||||
|
||||
**Automatic versioning**. All previous versions of all notes are stored and can be accessed by adding `?version=X` onto `/view` or `/edit`. If you are on the `/view` or `/edit` pages the menu below will show the most substantial changes in the history. Note, only the _current_ version can be edited (no branching allowed, yet).
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
**Self-destructing messages**. You can write a message that will delete itself when a user loads it (in any view). Useful for transmitting sensitive information. To use, simply add a line somewhere that says only "`self-destruct`".
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
**Security**. HTTPS support is provided and everything is sanitized to prevent XSS attacks. Though all URLs are publicly accessible, you are free to obfuscate your website by using an obscure/random address (read: the site is still publicly accessible, just hard to find!). In addition to TLS support, you can PGP-encrypt your messages using a passphrase (_Note: This will delete the version tree_).
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
**Syntax highlighting**. If you use a coding extension (e.g. .py, .md, .txt, .js, ...) then you'll automatically see syntax highlighting and line numbers.
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
**CLI tools**. Want to upload/download from the command line? Its super easy. Upload/download files using `curl` with a simple command:
|
||||
|
||||
```bash
|
||||
$ echo "Hello, world!" > hi.txt
|
||||
$ curl -L --upload-file hi.txt cowyo.com
|
||||
File uploaded to http://cowyo.com/hi.txt
|
||||
$ curl -L cowyo.com/test.txt
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
or just skip the file-creation step and let `cowyo` figure out a name for you:
|
||||
|
||||
```bash
|
||||
$ echo "Wow, so easy" | curl -L --upload-file "-" cowyo.com
|
||||
File uploaded to http://cowyo.com/CautiousCommonLoon
|
||||
$ curl -L cowyo.com/CautiousCommonLoon
|
||||
Wow, so easy
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
**Content Delivery**. Want use a script on your own domain? Just use the extension `/raw` with optional versioning (e.g. `/raw?version=1`). `cowyo` will serve these files with a wildcard Access-Control-Allow-Origin so they will work anywhere. Check out a live example [on this JSFiddle](https://jsfiddle.net/9mm3afao/).
|
||||
|
||||
**Keyboard Shortcuts**. Quickly transition between Edit/View/List by using `Ctl+Shift+E` to Edit, `Ctl+Shift+Z` to View, and `Ctl+Shift+L` to Listify.
|
||||
|
||||
**Admin controls**. The Admin can view/delete all the documents by setting the `-a YourAdminKey` when starting the program. Then the admin has access to the `/ls/YourAdminKey` to view and delete any of the pages.
|
||||
|
||||
**Math support**. Math is supported with [Katex](https://github.com/Khan/KaTeX) using `$\frac{1}{2}$` for inline equations and `$$\frac{1}{2}$$` for regular equations.
|
||||
|
||||
# Install
|
||||
|
||||
If you haven't done so, first [install Go](https://golang.org/doc/install).
|
||||
If you have go
|
||||
|
||||
```
|
||||
$ git clone https://github.com/schollz/cowyo.git
|
||||
$ cd cowyo
|
||||
$ go get ./...
|
||||
$ go build
|
||||
$ ./cowyo
|
||||
--------------------------
|
||||
cowyo (version Y) is up and running on http://SOMEADDRESS:8003
|
||||
Admin key: VRSgtuVpkrwtWrfphsooOYeErzdbVHHqecMcqcbkLFNFNPwakH
|
||||
--------------------------
|
||||
go get -u github.com/schollz/cowyo/...
|
||||
```
|
||||
|
||||
Now open your browser to `http://SOMEADDRESS:8003` to see your cowyo! For more information type `./cowyo --help`.
|
||||
or just [download the latest release](https://github.com/schollz/cowyo/releases/latest).
|
||||
|
||||
# Contact
|
||||
## Run
|
||||
|
||||
If you'd like help or you find a bug, please submit [an issue](https://github.com/schollz/cowyo/issues). Any other comments, questions or anything at all, just [tweet me @zack_118](https://twitter.com/intent/tweet?screen_name=zack_118)
|
||||
To run just double click or from the command line:
|
||||
|
||||
# Acknowledgements
|
||||
```
|
||||
cowyo
|
||||
```
|
||||
|
||||
Thanks to [tscholl2](https://github.com/tscholl2) and [sjsafranek](https://github.com/sjsafranek).
|
||||
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`.
|
||||
|
||||
Icons made by [Freepik](http://www.freepik.com) from [www.flaticon.com](http://www.flaticon.com), licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/ "Creative Commons BY 3.0").
|
||||
**Running with TLS**
|
||||
|
||||
File uploading from [transfer.sh](https://github.com/dutchcoders/transfer.sh/blob/98399c91dd86682077cf9542badbf1658fd9a8c1/transfersh-server/handlers.go#L293-L369), licensed by [MIT license](https://github.com/dutchcoders/transfer.sh/blob/40c9bf7675fb84e78d9a011052b9d0900ec7dde1/LICENSE).
|
||||
Specify a matching pair of SSL Certificate and Key to run cowyo using https. *cowyo* will now run in a secure session.
|
||||
|
||||
# License
|
||||
*N.B. Let's Encrypt is a CA that signs free and signed certificates.*
|
||||
|
||||
The MIT License (MIT)
|
||||
```
|
||||
cowyo --cert "/path/to/server.crt" --key "/p/t/server.key"
|
||||
```
|
||||
|
||||
Copyright (c) 2016 Zack
|
||||
**Running with Docker**
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
You can easily get started with Docker. First pull the latest image and create the volume with:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
```
|
||||
docker run -d -v /directory/to/data:/data -p 8050:8050 schollz/cowyo
|
||||
```
|
||||
|
||||
Then you can stop it with `docker stop cowyo` and start it again with `docker start cowyo`.
|
||||
|
||||
## Server customization
|
||||
|
||||
There are a couple of command-line flags that you can use to make *cowyo* your own micro-CMS.
|
||||
|
||||
```
|
||||
cowyo -lock 123 -default-page index.html -css mystyle.css -diary
|
||||
```
|
||||
|
||||
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".
|
||||
|
||||
## Usage
|
||||
|
||||
*cowyo* is straightforward to use. Here are some of the basic features:
|
||||
|
||||
### Publishing
|
||||
|
||||
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.
|
||||
|
||||
### View all the pages
|
||||
|
||||
To view the current list of all the pages goto to `/ls`.
|
||||
|
||||
### Editing
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
### History
|
||||
|
||||
You can easily see previous versions of your documents.
|
||||
|
||||

|
||||
|
||||
### Lists
|
||||
|
||||
You can easily make lists and check them off.
|
||||
|
||||

|
||||
|
||||
### Locking
|
||||
|
||||
Locking prevents other users from editing your pages without a passphrase.
|
||||
|
||||

|
||||
|
||||
### Encryption
|
||||
|
||||
Encryption is performed using AES-256.
|
||||
|
||||

|
||||
|
||||
### Self-destructing pages
|
||||
|
||||
Just like in mission impossible.
|
||||
|
||||

|
||||
|
||||
|
||||
## Development
|
||||
|
||||
You can run the tests using
|
||||
|
||||
```
|
||||
$ cd $GOPATH/src/github.com/schollz/cowyo
|
||||
$ go test ./...
|
||||
```
|
||||
|
||||
Any contributions are welcome.
|
||||
|
||||
## Thanks
|
||||
|
||||
...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!
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
@ -1,9 +0,0 @@
|
||||
# Release 1.0
|
||||
|
||||
New features:
|
||||
|
||||
- New route /raw to use as a content delivery system
|
||||
- Coding support provided through [CodeMirror](https://codemirror.net/)
|
||||
- CLI support for uploading/downloading files with `curl`
|
||||
- New route to force WSS sockets (for use with Caddy)
|
||||
- Changed name, improved documentation
|
40
cmd/herdyo/herdyo.go
Normal file
40
cmd/herdyo/herdyo.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/schollz/cowyo/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
store := sessions.NewStore([]byte("secret"))
|
||||
|
||||
first := server.Site{
|
||||
PathToData: "site1",
|
||||
Debounce: 500,
|
||||
SessionStore: store,
|
||||
AllowInsecure: true,
|
||||
Fileuploads: true,
|
||||
MaxUploadSize: 2,
|
||||
}.Router()
|
||||
|
||||
second := server.Site{
|
||||
PathToData: "site2",
|
||||
Debounce: 500,
|
||||
SessionStore: store,
|
||||
AllowInsecure: true,
|
||||
Fileuploads: true,
|
||||
MaxUploadSize: 2,
|
||||
}.Router()
|
||||
panic(http.ListenAndServe("localhost:8000", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.Host, "first") {
|
||||
first.ServeHTTP(rw, r)
|
||||
} else if strings.HasPrefix(r.Host, "second") {
|
||||
second.ServeHTTP(rw, r)
|
||||
} else {
|
||||
http.NotFound(rw, r)
|
||||
}
|
||||
})))
|
||||
}
|
14
cmd/tomlo/tomlo.go
Normal file
14
cmd/tomlo/tomlo.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/schollz/cowyo/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c, err := config.ParseFile("multisite_sample.toml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
panic(c.ListenAndServe())
|
||||
}
|
55
config/config.go
Normal file
55
config/config.go
Normal file
@ -0,0 +1,55 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
func ParseFile(path string) (Config, error) {
|
||||
c := Config{}
|
||||
if _, err := toml.DecodeFile("multisite_sample.toml", &c); err != nil {
|
||||
// handle error
|
||||
return c, err
|
||||
}
|
||||
c.SetDefaults()
|
||||
c.Validate()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Default SiteConfig
|
||||
Sites []SiteConfig
|
||||
}
|
||||
|
||||
type SiteConfig struct {
|
||||
Host *string
|
||||
Port *int
|
||||
DataDir *string
|
||||
DefaultPage *string
|
||||
AllowInsecureMarkup *bool
|
||||
Lock *string
|
||||
DebounceSave *int
|
||||
Diary *bool
|
||||
AccessCode *string
|
||||
FileUploadsAllowed *bool
|
||||
MaxFileUploadMb *uint
|
||||
MaxDocumentLength *uint
|
||||
TLS *TLSConfig
|
||||
CookieKeys []CookieKey
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
CertPath string
|
||||
KeyPath string
|
||||
Port int
|
||||
}
|
||||
|
||||
type CookieKey struct {
|
||||
AuthenticateBase64 string
|
||||
EncryptBase64 string
|
||||
}
|
||||
|
||||
func (c Config) Validate() {
|
||||
for _, v := range c.Sites {
|
||||
v.sessionStore()
|
||||
}
|
||||
}
|
102
config/defaults.go
Normal file
102
config/defaults.go
Normal file
@ -0,0 +1,102 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"crypto/rand"
|
||||
)
|
||||
var DefaultSiteConfig SiteConfig
|
||||
|
||||
|
||||
func makeAuthKey() string {
|
||||
secret := make([]byte, 32)
|
||||
_, err := rand.Read(secret)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(secret)
|
||||
}
|
||||
|
||||
func init() {
|
||||
host := "*"
|
||||
port := 8050
|
||||
debounce := 500
|
||||
dataDir := "data"
|
||||
empty := ""
|
||||
zer := uint(0)
|
||||
lots := uint(100000000)
|
||||
fal := false
|
||||
|
||||
ck := CookieKey{
|
||||
AuthenticateBase64: "",
|
||||
EncryptBase64: "",
|
||||
}
|
||||
|
||||
DefaultSiteConfig = SiteConfig{
|
||||
Host:&host,
|
||||
Port:&port,
|
||||
DataDir:&dataDir,
|
||||
DebounceSave:&debounce,
|
||||
CookieKeys: []CookieKey{ck},
|
||||
DefaultPage:&empty,
|
||||
AllowInsecureMarkup:&fal,
|
||||
Lock:&empty,
|
||||
Diary:&fal,
|
||||
AccessCode:&empty,
|
||||
FileUploadsAllowed:&fal,
|
||||
MaxFileUploadMb:&zer,
|
||||
MaxDocumentLength:&lots,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func copyDefaults(base, defaults *SiteConfig) {
|
||||
if base.Host == nil {
|
||||
base.Host = defaults.Host
|
||||
}
|
||||
if base.Port == nil {
|
||||
base.Port = defaults.Port
|
||||
}
|
||||
if base.DataDir == nil {
|
||||
base.DataDir = defaults.DataDir
|
||||
}
|
||||
if base.DefaultPage == nil {
|
||||
base.DefaultPage = defaults.DefaultPage
|
||||
}
|
||||
if base.AllowInsecureMarkup == nil {
|
||||
base.AllowInsecureMarkup = defaults.AllowInsecureMarkup
|
||||
}
|
||||
if base.Lock == nil {
|
||||
base.Lock = defaults.Lock
|
||||
}
|
||||
if base.DebounceSave == nil {
|
||||
base.DebounceSave = defaults.DebounceSave
|
||||
}
|
||||
if base.Diary == nil {
|
||||
base.Diary = defaults.Diary
|
||||
}
|
||||
if base.AccessCode == nil {
|
||||
base.AccessCode = defaults.AccessCode
|
||||
}
|
||||
if base.FileUploadsAllowed == nil {
|
||||
base.FileUploadsAllowed = defaults.FileUploadsAllowed
|
||||
}
|
||||
if base.MaxFileUploadMb == nil {
|
||||
base.MaxFileUploadMb = defaults.MaxFileUploadMb
|
||||
}
|
||||
if base.MaxDocumentLength == nil {
|
||||
base.MaxDocumentLength = defaults.MaxDocumentLength
|
||||
}
|
||||
if base.TLS == nil {
|
||||
base.TLS = defaults.TLS
|
||||
}
|
||||
if base.CookieKeys == nil {
|
||||
base.CookieKeys = defaults.CookieKeys
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) SetDefaults() {
|
||||
copyDefaults(&c.Default, &DefaultSiteConfig)
|
||||
for i := range c.Sites {
|
||||
copyDefaults(&c.Sites[i], &c.Default)
|
||||
}
|
||||
}
|
105
config/http.go
Normal file
105
config/http.go
Normal file
@ -0,0 +1,105 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/jcelliott/lumber"
|
||||
"github.com/schollz/cowyo/server"
|
||||
"strings"
|
||||
)
|
||||
func (c Config) ListenAndServe() error {
|
||||
insecurePorts := map[int]bool{}
|
||||
securePorts := map[int]bool{}
|
||||
err := make(chan error)
|
||||
for _, s := range c.Sites {
|
||||
if !insecurePorts[*s.Port] {
|
||||
insecurePorts[*s.Port] = true
|
||||
go func(s SiteConfig) {
|
||||
err <- http.ListenAndServe(fmt.Sprintf("localhost:%d", *s.Port), c)
|
||||
}(s)
|
||||
}
|
||||
if s.TLS != nil && !securePorts[s.TLS.Port] {
|
||||
securePorts[s.TLS.Port] = true
|
||||
go func(s SiteConfig) {
|
||||
err <- http.ListenAndServeTLS(
|
||||
fmt.Sprintf("localhost:%d", s.TLS.Port),
|
||||
s.TLS.CertPath,
|
||||
s.TLS.KeyPath,
|
||||
c,
|
||||
)
|
||||
}(s)
|
||||
}
|
||||
}
|
||||
for {
|
||||
return <- err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
for i := range c.Sites {
|
||||
if c.Sites[i].MatchesRequest(r) {
|
||||
c.Sites[i].Handle(rw, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
http.NotFound(rw, r)
|
||||
}
|
||||
|
||||
func (s SiteConfig) MatchesRequest(r *http.Request) bool {
|
||||
sh := *s.Host
|
||||
if strings.HasPrefix(sh, "*") {
|
||||
return strings.HasSuffix(r.Host, sh[1:])
|
||||
}
|
||||
return sh == r.Host
|
||||
}
|
||||
|
||||
func (s SiteConfig) sessionStore() sessions.Store {
|
||||
keys := [][]byte{}
|
||||
for _, k := range s.CookieKeys {
|
||||
key, err := base64.StdEncoding.DecodeString(k.AuthenticateBase64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(key) != 32 {
|
||||
log.Panicf("AuthenticateBase64 key %s must be 32 bytes; suggest %s", k.AuthenticateBase64, makeAuthKey())
|
||||
}
|
||||
|
||||
keys = append(keys, key)
|
||||
key, err = base64.StdEncoding.DecodeString(k.EncryptBase64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(key) != 32 {
|
||||
log.Panicf("EncryptBase64 key %s must be 32 bytes, suggest %s", k.EncryptBase64, makeAuthKey())
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return sessions.NewStore(keys...)
|
||||
}
|
||||
|
||||
func (s SiteConfig) Handle(rw http.ResponseWriter, r *http.Request) {
|
||||
dataDir := strings.Replace(*s.DataDir, "${HOST}", r.Host, -1)
|
||||
|
||||
router := server.Site{
|
||||
PathToData: dataDir,
|
||||
Css: []byte{},
|
||||
DefaultPage: *s.DefaultPage,
|
||||
DefaultPassword: *s.Lock,
|
||||
Debounce: *s.DebounceSave,
|
||||
Diary: *s.Diary,
|
||||
SessionStore: s.sessionStore(),
|
||||
SecretCode: *s.AccessCode,
|
||||
AllowInsecure: *s.AllowInsecureMarkup,
|
||||
Fileuploads: *s.MaxFileUploadMb > 0,
|
||||
MaxUploadSize: *s.MaxFileUploadMb,
|
||||
Logger: lumber.NewConsoleLogger(server.LogLevel),
|
||||
MaxDocumentSize: *s.MaxDocumentLength,
|
||||
}.Router()
|
||||
|
||||
router.ServeHTTP(rw, r)
|
||||
}
|
186
db.go
186
db.go
@ -1,186 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var db *bolt.DB
|
||||
var open bool
|
||||
|
||||
// Open to create the database and open
|
||||
func Open(filename string) error {
|
||||
var err error
|
||||
config := &bolt.Options{Timeout: 30 * time.Second}
|
||||
db, err = bolt.Open(filename, 0600, config)
|
||||
if err != nil {
|
||||
fmt.Println("Opening BoltDB timed out")
|
||||
log.Fatal(err)
|
||||
}
|
||||
open = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close database
|
||||
func Close() {
|
||||
open = false
|
||||
db.Close()
|
||||
}
|
||||
|
||||
// WikiData is data for storing in DB
|
||||
type WikiData struct {
|
||||
Title string
|
||||
CurrentText string
|
||||
Diffs []string
|
||||
Timestamps []string
|
||||
Encrypted bool
|
||||
Locked string
|
||||
}
|
||||
|
||||
func getCurrentText(title string, version int) (string, []versionsInfo, bool, time.Duration, bool, string, int) {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
title = strings.ToLower(title)
|
||||
var vi []versionsInfo
|
||||
totalTime := time.Now().Sub(time.Now())
|
||||
isCurrent := true
|
||||
currentText := ""
|
||||
encrypted := false
|
||||
locked := ""
|
||||
currentVersionNum := -1
|
||||
if !open {
|
||||
return currentText, vi, isCurrent, totalTime, encrypted, locked, currentVersionNum
|
||||
}
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
if b == nil {
|
||||
return fmt.Errorf("db must be opened before loading")
|
||||
}
|
||||
k := []byte(title)
|
||||
val := b.Get(k)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
var p WikiData
|
||||
err = p.decode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentText = p.CurrentText
|
||||
encrypted = p.Encrypted
|
||||
locked = p.Locked
|
||||
currentVersionNum = len(p.Diffs) - 1
|
||||
if version > -1 && version < len(p.Diffs) {
|
||||
// get that version of text instead
|
||||
currentText = rebuildTextsToDiffN(p, version)
|
||||
isCurrent = false
|
||||
}
|
||||
vi, totalTime = getImportantVersions(p)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Could not get WikiData: %s", err)
|
||||
}
|
||||
return currentText, vi, isCurrent, totalTime, encrypted, locked, currentVersionNum
|
||||
}
|
||||
|
||||
func (p *WikiData) load(title string) error {
|
||||
title = strings.ToLower(title)
|
||||
if !open {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
k := []byte(title)
|
||||
val := b.Get(k)
|
||||
if val == nil {
|
||||
// make new one
|
||||
p.Title = title
|
||||
p.CurrentText = ""
|
||||
p.Diffs = []string{}
|
||||
p.Timestamps = []string{}
|
||||
return nil
|
||||
}
|
||||
err = p.decode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Could not get WikiData: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WikiData) save(newText string) error {
|
||||
if !open {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte("datas"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket: %s", err)
|
||||
}
|
||||
// find diffs
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(p.CurrentText, newText, true)
|
||||
delta := dmp.DiffToDelta(diffs)
|
||||
p.CurrentText = newText
|
||||
p.Timestamps = append(p.Timestamps, time.Now().Format(time.ANSIC))
|
||||
p.Diffs = append(p.Diffs, delta)
|
||||
enc, err := p.encode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not encode WikiData: %s", err)
|
||||
}
|
||||
p.Title = strings.ToLower(p.Title)
|
||||
err = bucket.Put([]byte(p.Title), enc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could add to bucket: %s", err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
// // Add the new name to the programdata so its not randomly generated
|
||||
// if err == nil && len(p.Timestamps) > 0 && len(p.CurrentText) > 0 {
|
||||
// err2 := db.Update(func(tx *bolt.Tx) error {
|
||||
// b := tx.Bucket([]byte("programdata"))
|
||||
// id, _ := b.NextSequence()
|
||||
// idInt := int(id)
|
||||
// return b.Put(itob(idInt), []byte(p.Title))
|
||||
// })
|
||||
// if err2 != nil {
|
||||
// return fmt.Errorf("could not add to programdata: %s", err)
|
||||
// }
|
||||
// }
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *WikiData) encode() ([]byte, error) {
|
||||
enc, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
func (p *WikiData) decode(data []byte) error {
|
||||
err := json.Unmarshal(data, &p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
@ -0,0 +1,9 @@
|
||||
version: "2"
|
||||
|
||||
services:
|
||||
cowyo:
|
||||
build: .
|
||||
ports:
|
||||
- 8050:8050
|
||||
volumes:
|
||||
- ./data:/data
|
28
encrypt/encrypt.go
Normal file
28
encrypt/encrypt.go
Normal file
@ -0,0 +1,28 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/schollz/cryptopasta"
|
||||
)
|
||||
|
||||
func EncryptString(toEncrypt string, password string) (string, error) {
|
||||
key := sha256.Sum256([]byte(password))
|
||||
encrypted, err := cryptopasta.Encrypt([]byte(toEncrypt), &key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
func DecryptString(toDecrypt string, password string) (string, error) {
|
||||
key := sha256.Sum256([]byte(password))
|
||||
contentData, err := hex.DecodeString(toDecrypt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bDecrypted, err := cryptopasta.Decrypt(contentData, &key)
|
||||
return string(bDecrypted), err
|
||||
}
|
22
encrypt/encrypt_test.go
Normal file
22
encrypt/encrypt_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
package encrypt
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEncryption(t *testing.T) {
|
||||
s, err := EncryptString("some string", "some password")
|
||||
if err != nil {
|
||||
t.Errorf("What")
|
||||
}
|
||||
d, err := DecryptString(s, "some wrong password")
|
||||
if err == nil {
|
||||
t.Errorf("Should throw error for bad password")
|
||||
}
|
||||
d, err = DecryptString(s, "some password")
|
||||
if err != nil {
|
||||
t.Errorf("Should not throw password")
|
||||
}
|
||||
if d != "some string" {
|
||||
t.Errorf("Problem decoding")
|
||||
}
|
||||
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
)
|
||||
|
||||
var encryptionType string
|
||||
|
||||
func init() {
|
||||
encryptionType = "PGP SIGNATURE"
|
||||
}
|
||||
|
||||
func encryptString(encryptionText string, encryptionPassphraseString string) string {
|
||||
encryptionPassphrase := []byte(encryptionPassphraseString)
|
||||
encbuf := bytes.NewBuffer(nil)
|
||||
w, err := armor.Encode(encbuf, encryptionType, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
plaintext, err := openpgp.SymmetricallyEncrypt(w, encryptionPassphrase, nil, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
message := []byte(encryptionText)
|
||||
_, err = plaintext.Write(message)
|
||||
|
||||
plaintext.Close()
|
||||
w.Close()
|
||||
return encbuf.String()
|
||||
}
|
||||
|
||||
func decryptString(decryptionString string, encryptionPassphraseString string) (string, error) {
|
||||
encryptionPassphrase := []byte(encryptionPassphraseString)
|
||||
decbuf := bytes.NewBuffer([]byte(decryptionString))
|
||||
result, err := armor.Decode(decbuf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
alreadyPrompted := false
|
||||
md, err := openpgp.ReadMessage(result.Body, nil, func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
||||
if alreadyPrompted {
|
||||
return nil, errors.New("Could not decrypt using passphrase")
|
||||
} else {
|
||||
alreadyPrompted = true
|
||||
}
|
||||
return encryptionPassphrase, nil
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(md.UnverifiedBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// func main() {
|
||||
// test := encryptString("This is some string", "golang")
|
||||
// fmt.Println(test)
|
||||
// testD := decryptString(test, "golang")
|
||||
// fmt.Println(testD)
|
||||
//
|
||||
// }
|
35
go.mod
Normal file
35
go.mod
Normal file
@ -0,0 +1,35 @@
|
||||
module github.com/schollz/cowyo
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/bytedance/sonic v1.9.2 // indirect
|
||||
github.com/danielheath/gin-teeny-security v0.0.0-20180331042316-bb11804dd0e2
|
||||
github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271
|
||||
github.com/gin-contrib/sessions v0.0.5
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.24
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/schollz/cryptopasta v0.0.0-20170217152710-dcd61c7d42a1
|
||||
github.com/schollz/versionedtext v0.0.0-20180523061923-d8ce0957c254
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629
|
||||
github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 // indirect
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 // indirect
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect
|
||||
github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b // indirect
|
||||
github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/urfave/cli.v1 v1.20.0
|
||||
)
|
405
go.sum
Normal file
405
go.sum
Normal file
@ -0,0 +1,405 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
github.com/bos-hieu/mongostore v0.0.2/go.mod h1:8AbbVmDEb0yqJsBrWxZIAZOxIfv/tsP8CDtdHduZHGg=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
|
||||
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/danielheath/gin-teeny-security v0.0.0-20180331042316-bb11804dd0e2 h1:OU8xbewlvG+K/mPYGshgkSDJZugXeV4cvlI8r02a58o=
|
||||
github.com/danielheath/gin-teeny-security v0.0.0-20180331042316-bb11804dd0e2/go.mod h1:iufTPweOVe3TKbMOYF0OyJ5iM4pdK/D9F9dIQoQC4IE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271 h1:s+boMV47gwTyff2PL+k6V33edJpp+K5y3QPzZlRhno8=
|
||||
github.com/gin-contrib/multitemplate v0.0.0-20230212012517-45920c92c271/go.mod h1:XLLtIXoP9+9zGcEDc7gAGV3AksGPO+vzv4kXHMJSdU0=
|
||||
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
|
||||
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
|
||||
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
|
||||
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
|
||||
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
|
||||
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
|
||||
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
|
||||
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
|
||||
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8=
|
||||
github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw=
|
||||
github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/schollz/cryptopasta v0.0.0-20170217152710-dcd61c7d42a1 h1:CAVM5ALs/TKIa2ri7WMqge+m5wz/ItuiU6CFUPjZTjA=
|
||||
github.com/schollz/cryptopasta v0.0.0-20170217152710-dcd61c7d42a1/go.mod h1:sM7qObCXSAwGYckYHG4m0hP3PSCBcHmC7/w/kBwcwgM=
|
||||
github.com/schollz/versionedtext v0.0.0-20180523061923-d8ce0957c254 h1:/EgihFrDLhb/x7NLm8cWB7QTquw5gatR+y/jv2gLWsY=
|
||||
github.com/schollz/versionedtext v0.0.0-20180523061923-d8ce0957c254/go.mod h1:116sjYSGDGoVSTUCdO34dA1Yg1ZGbN2jk/aYThLfK60=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 h1:86e54L0i3pH3dAIA8OxBbfLrVyhoGpnNk1iJCigAWYs=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 h1:SpaoQDTgpo2YZkvmr2mtgloFFfPTjtLMlZkQtNAPQik=
|
||||
github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 h1:KaKXZldeYH73dpQL+Nr38j1r5BgpAYQjYvENOUpIZDQ=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b h1:rBIwpb5ggtqf0uZZY5BPs1sL7njUMM7I8qD2jiou70E=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8 h1:W5meM/5DP0Igf+pS3Se363Y2DoDv9LUuZgQ24uG9LNY=
|
||||
github.com/shurcooL/octicon v0.0.0-20230705024016-66bff059edb8/go.mod h1:hWBWTvIJ918VxbNOk2hxQg1/5j1M9yQI1Kp8d9qrOq8=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/wader/gormstore/v2 v2.0.0/go.mod h1:3BgNKFxRdVo2E4pq3e/eiim8qRDZzaveaIcIvu2T8r0=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
|
||||
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
|
||||
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
|
||||
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
||||
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
@ -1,101 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: awwkoala
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start daemon at boot time
|
||||
# Description: Enable service provided by daemon.
|
||||
### END INIT INFO
|
||||
|
||||
# This should be placed in /etc/init.d directory
|
||||
# start with
|
||||
# sudo /etc/init.d/awwkoala start
|
||||
# stop with
|
||||
# sudo /etc/init.d/awwkoala start
|
||||
|
||||
name="cowyo"
|
||||
dir="CUR_DIR"
|
||||
user="USERCUR"
|
||||
cmd="./$name -p :PORT EXT_ADDRESS"
|
||||
|
||||
pid_file="/var/run/$name.pid"
|
||||
stdout_log="/var/log/$name.log"
|
||||
stderr_log="/var/log/$name.err"
|
||||
|
||||
get_pid() {
|
||||
cat "$pid_file"
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "Already started"
|
||||
else
|
||||
echo "Starting $name"
|
||||
cd "$dir"
|
||||
sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_running; then
|
||||
echo -n "Stopping $name.."
|
||||
kill `get_pid`
|
||||
for i in {1..10}
|
||||
do
|
||||
if ! is_running; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
|
||||
if is_running; then
|
||||
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Stopped"
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
if is_running; then
|
||||
echo "Unable to stop, will not attempt to start"
|
||||
exit 1
|
||||
fi
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Stopped"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
@ -1,41 +0,0 @@
|
||||
server {
|
||||
# SERVER BLOCK FOR cowyo
|
||||
listen 80; ## listen for ipv4; this line is default and implied
|
||||
|
||||
access_log /etc/nginx/logs/access-cowyo.log;
|
||||
error_log /etc/nginx/logs/error-cowyo.log info;
|
||||
root CUR_DIR;
|
||||
server_name ADDRESS;
|
||||
|
||||
# Media: images, icons, video, audio, HTC
|
||||
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
|
||||
expires 1M;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# CSS and Javascript
|
||||
location ~* \.(?:css|js)$ {
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
location ^~ /static {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~ ^/ {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
|
||||
proxy_pass http://127.0.0.1:PORT;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
ADDRESS = yourserver.com
|
||||
PORT = 8003
|
||||
|
||||
CUR_DIR = $(shell bash -c 'pwd')
|
||||
USERCUR = $(shell bash -c 'whoami')
|
||||
|
||||
make:
|
||||
go build
|
||||
|
||||
install:
|
||||
rm -rf jinstall
|
||||
mkdir jinstall
|
||||
cp cowyo.ssl.nginx cowyo.ssl.nginx.temp
|
||||
sed -i 's/PORT/$(PORT)/g' cowyo.ssl.nginx.temp
|
||||
sed -i 's/ADDRESS/$(ADDRESS)/g' cowyo.ssl.nginx.temp
|
||||
sed -i 's^CUR_DIR^$(CUR_DIR)^g' cowyo.ssl.nginx.temp
|
||||
cp cowyo.init cowyo.init.temp
|
||||
sed -i 's/EXT_ADDRESS/$(ADDRESS)/g' cowyo.init.temp
|
||||
sed -i 's^CUR_DIR^$(CUR_DIR)^g' cowyo.init.temp
|
||||
sed -i 's^USERCUR^$(USERCUR)^g' cowyo.init.temp
|
||||
sed -i 's^PORT^$(PORT)^g' cowyo.init.temp
|
||||
cp cowyo.init.temp /etc/init.d/cowyo.init
|
||||
chmod +x /etc/init.d/cowyo.init
|
||||
cp cowyo.ssl.nginx.temp /etc/nginx/sites-available/cowyo.nginx
|
||||
ln -fs /etc/nginx/sites-available/cowyo.nginx /etc/nginx/sites-enabled/cowyo.nginx
|
||||
/etc/init.d/nginx reload
|
||||
/etc/init.d/nginx restart
|
||||
/etc/init.d/cowyo.init restart
|
||||
rm -rf *.temp
|
||||
|
||||
.PHONY: install
|
@ -1,17 +0,0 @@
|
||||
First install the NGINX block in this directory. (There is an experimental Makefile that will do this, just try `sudo make install`.
|
||||
|
||||
To use letsencrypt follow these steps:
|
||||
|
||||
```
|
||||
git clone https://github.com/letsencrypt/letsencrypt
|
||||
cd letsencrypt
|
||||
sudo service nginx stop
|
||||
sudo ./letsencrypt-auto certonly --standalone --email youremail@somewhere.com -d yourserver.com
|
||||
sudo service nginx start
|
||||
```
|
||||
|
||||
Then startup `cowyo` with
|
||||
|
||||
```bash
|
||||
sudo ./cowyo -p :8001 -key /etc/letsencrypt/live/yourserver.com/privkey.pem -crt /etc/letsencrypt/live/yourserver.com/cert.pem yourserver.com
|
||||
```
|
@ -1,101 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: cowyo
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start daemon at boot time
|
||||
# Description: Enable service provided by daemon.
|
||||
### END INIT INFO
|
||||
|
||||
# This should be placed in /etc/init.d directory
|
||||
# start with
|
||||
# sudo /etc/init.d/cowyo start
|
||||
# stop with
|
||||
# sudo /etc/init.d/cowyo start
|
||||
|
||||
dir="CUR_DIR"
|
||||
user="USERCUR"
|
||||
cmd="./cowyo -p :PORT -key /etc/letsencrypt/live/EXT_ADDRESS/privkey.pem -crt /etc/letsencrypt/live/EXT_ADDRESS/cert.pem yourserver.com./cowyo EXT_ADDRESS"
|
||||
|
||||
name="cowyo"
|
||||
pid_file="/var/run/$name.pid"
|
||||
stdout_log="/var/log/$name.log"
|
||||
stderr_log="/var/log/$name.err"
|
||||
|
||||
get_pid() {
|
||||
cat "$pid_file"
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "Already started"
|
||||
else
|
||||
echo "Starting $name"
|
||||
cd "$dir"
|
||||
sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_running; then
|
||||
echo -n "Stopping $name.."
|
||||
kill `get_pid`
|
||||
for i in {1..10}
|
||||
do
|
||||
if ! is_running; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
|
||||
if is_running; then
|
||||
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Stopped"
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
if is_running; then
|
||||
echo "Unable to stop, will not attempt to start"
|
||||
exit 1
|
||||
fi
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Stopped"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
@ -1,50 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name ADDRESS;
|
||||
rewrite ^ https://$server_name$request_uri? permanent;
|
||||
}
|
||||
|
||||
server {
|
||||
# SERVER BLOCK FOR ADDRESS
|
||||
listen 443 ssl;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_certificate /etc/letsencrypt/live/ADDRESS/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/ADDRESS/privkey.pem;
|
||||
|
||||
access_log /etc/nginx/logs/access-ADDRESS.log;
|
||||
error_log /etc/nginx/logs/error-ADDRESS.log info;
|
||||
root CUR_DIR;
|
||||
server_name ADDRESS;
|
||||
|
||||
# Media: images, icons, video, audio, HTC
|
||||
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
|
||||
expires 1M;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# CSS and Javascript
|
||||
location ~* \.(?:css|js)$ {
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
location ^~ /static {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~ ^/ {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
|
||||
proxy_pass https://127.0.0.1:PORT;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
354
main.go
Normal file → Executable file
354
main.go
Normal file → Executable file
@ -1,181 +1,209 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jcelliott/lumber"
|
||||
"github.com/schollz/cowyo/server"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// AllowedIPs is a white/black list of
|
||||
// IP addresses allowed to access cowyo
|
||||
var AllowedIPs = map[string]bool{
|
||||
"192.168.1.13": true,
|
||||
"192.168.1.12": true,
|
||||
"192.168.1.2": true,
|
||||
}
|
||||
|
||||
// RuntimeArgs contains all runtime
|
||||
// arguments available
|
||||
var RuntimeArgs struct {
|
||||
WikiName string
|
||||
ExternalIP string
|
||||
Port string
|
||||
DatabaseLocation string
|
||||
ServerCRT string
|
||||
ServerKey string
|
||||
SourcePath string
|
||||
AdminKey string
|
||||
Socket string
|
||||
ForceWss bool
|
||||
DumpDataset string
|
||||
RestoreDataset string
|
||||
Debug bool
|
||||
}
|
||||
var VersionNum string
|
||||
|
||||
func init() {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
var version string
|
||||
var pathToData string
|
||||
|
||||
func main() {
|
||||
// _, executableFile, _, _ := runtime.Caller(0) // get full path of this file
|
||||
cwd, _ := os.Getwd()
|
||||
databaseFile := path.Join(cwd, "data.db")
|
||||
flag.StringVar(&RuntimeArgs.Port, "p", ":8003", "port to bind")
|
||||
flag.StringVar(&RuntimeArgs.DatabaseLocation, "db", databaseFile, "location of database file")
|
||||
flag.StringVar(&RuntimeArgs.AdminKey, "a", "", "key to access admin privaleges")
|
||||
flag.StringVar(&RuntimeArgs.ServerCRT, "crt", "", "location of SSL certificate")
|
||||
flag.StringVar(&RuntimeArgs.ServerKey, "key", "", "location of SSL key")
|
||||
flag.StringVar(&RuntimeArgs.WikiName, "w", "cowyo", "custom name for wiki")
|
||||
flag.BoolVar(&RuntimeArgs.ForceWss, "e", false, "force encrypted sockets (use if using Caddy auto HTTPS)")
|
||||
flag.BoolVar(&RuntimeArgs.Debug, "d", false, "debugging mode")
|
||||
flag.StringVar(&RuntimeArgs.DumpDataset, "dump", "", "directory to dump all data to")
|
||||
flag.StringVar(&RuntimeArgs.RestoreDataset, "restore", "", "directory to restore all data from")
|
||||
flag.CommandLine.Usage = func() {
|
||||
fmt.Println(`cowyo (version ` + VersionNum + `)
|
||||
|
||||
Usage: cowyo [options] [address]
|
||||
|
||||
If address is not provided then cowyo
|
||||
will determine the best internal IP address.
|
||||
|
||||
Example: 'cowyo'
|
||||
Example: 'cowyo yourserver.com'
|
||||
Example: 'cowyo -p :8080 localhost:8080'
|
||||
Example: 'cowyo -p :8080 -crt ssl/server.crt -key ssl/server.key localhost:8080'
|
||||
|
||||
Options:`)
|
||||
flag.CommandLine.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
// Set the log level
|
||||
if RuntimeArgs.Debug == false {
|
||||
logger.Level(2)
|
||||
} else {
|
||||
logger.Level(0)
|
||||
}
|
||||
|
||||
if len(RuntimeArgs.DumpDataset) > 0 {
|
||||
fmt.Println("Dumping data to '" + RuntimeArgs.DumpDataset + "' folder...")
|
||||
dumpEverything(RuntimeArgs.DumpDataset)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
RuntimeArgs.ExternalIP = flag.Arg(0)
|
||||
if RuntimeArgs.ExternalIP == "" {
|
||||
logger.Debug("Getting external ip...")
|
||||
RuntimeArgs.ExternalIP = GetLocalIP() + RuntimeArgs.Port
|
||||
logger.Debug("Using ip: %s and port %s", GetLocalIP(), RuntimeArgs.Port)
|
||||
}
|
||||
RuntimeArgs.SourcePath = cwd
|
||||
|
||||
if len(RuntimeArgs.AdminKey) == 0 {
|
||||
RuntimeArgs.AdminKey = RandStringBytesMaskImprSrc(50)
|
||||
}
|
||||
// create programdata bucket
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte("programdata"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket: %s", err)
|
||||
app := cli.NewApp()
|
||||
app.Name = "cowyo"
|
||||
app.Usage = "a simple wiki"
|
||||
app.Version = version
|
||||
app.Compiled = time.Now()
|
||||
app.Action = func(c *cli.Context) error {
|
||||
pathToData = c.GlobalString("data")
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
host := c.GlobalString("host")
|
||||
crt_f := c.GlobalString("cert") // crt flag
|
||||
key_f := c.GlobalString("key") // key flag
|
||||
if host == "" {
|
||||
host = GetLocalIP()
|
||||
}
|
||||
return err
|
||||
})
|
||||
TLS := false
|
||||
if crt_f != "" && key_f != "" {
|
||||
TLS = true
|
||||
}
|
||||
if TLS {
|
||||
fmt.Printf("\nRunning cowyo server (version %s) at https://%s:%s\n\n", version, host, c.GlobalString("port"))
|
||||
} else {
|
||||
fmt.Printf("\nRunning cowyo server (version %s) at http://%s:%s\n\n", version, host, c.GlobalString("port"))
|
||||
}
|
||||
|
||||
server.Serve(
|
||||
pathToData,
|
||||
c.GlobalString("host"),
|
||||
c.GlobalString("port"),
|
||||
c.GlobalString("cert"),
|
||||
c.GlobalString("key"),
|
||||
TLS,
|
||||
c.GlobalString("css"),
|
||||
c.GlobalString("default-page"),
|
||||
c.GlobalString("lock"),
|
||||
c.GlobalInt("debounce"),
|
||||
c.GlobalBool("diary"),
|
||||
c.GlobalString("cookie-secret"),
|
||||
c.GlobalString("access-code"),
|
||||
c.GlobalBool("allow-insecure-markup"),
|
||||
c.GlobalBool("allow-file-uploads"),
|
||||
c.GlobalUint("max-upload-mb"),
|
||||
c.GlobalUint("max-document-length"),
|
||||
logger(c.GlobalBool("debug")),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "data",
|
||||
Value: "data",
|
||||
Usage: "data folder to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "olddata",
|
||||
Value: "",
|
||||
Usage: "data folder for migrating",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "",
|
||||
Usage: "host to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "port,p",
|
||||
Value: "8050",
|
||||
Usage: "port to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cert",
|
||||
Value: "",
|
||||
Usage: "absolute path to SSL public sertificate",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "key",
|
||||
Value: "",
|
||||
Usage: "absolute path to SSL private key",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "css",
|
||||
Value: "",
|
||||
Usage: "use a custom CSS file",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "default-page",
|
||||
Value: "",
|
||||
Usage: "show default-page/read instead of editing (default: show random editing)",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "allow-insecure-markup",
|
||||
Usage: "Skip HTML sanitization",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "lock",
|
||||
Value: "",
|
||||
Usage: "password to lock editing all files (default: all pages unlocked)",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "debounce",
|
||||
Value: 500,
|
||||
Usage: "debounce time for saving data, in milliseconds",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "turn on debugging",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "diary",
|
||||
Usage: "turn diary mode (doing New will give a timestamped page)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "access-code",
|
||||
Value: "",
|
||||
Usage: "Secret code to login with before accessing any wiki stuff",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cookie-secret",
|
||||
Value: "secret",
|
||||
Usage: "random data to use for cookies; changing it will invalidate all sessions",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "allow-file-uploads",
|
||||
Usage: "Enable file uploads",
|
||||
},
|
||||
cli.UintFlag{
|
||||
Name: "max-upload-mb",
|
||||
Value: 2,
|
||||
Usage: "Largest file upload (in mb) allowed",
|
||||
},
|
||||
cli.UintFlag{
|
||||
Name: "max-document-length",
|
||||
Value: 100000000,
|
||||
Usage: "Largest wiki page (in characters) allowed",
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "migrate",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "migrate from the old cowyo",
|
||||
Action: func(c *cli.Context) error {
|
||||
pathToData = c.GlobalString("data")
|
||||
pathToOldData := c.GlobalString("olddata")
|
||||
if len(pathToOldData) == 0 {
|
||||
fmt.Printf("You need to specify folder with -olddata")
|
||||
return nil
|
||||
}
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
if !exists(pathToOldData) {
|
||||
fmt.Printf("Can not find '%s', does it exist?", pathToOldData)
|
||||
return nil
|
||||
}
|
||||
server.Migrate(pathToOldData, pathToData, logger(c.GlobalBool("debug")))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
// GetLocalIP returns the local ip address
|
||||
func GetLocalIP() string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return ""
|
||||
}
|
||||
Close()
|
||||
|
||||
// Default page
|
||||
defaultPage, _ := ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "templates/aboutpage.md"))
|
||||
p := WikiData{"help", "", []string{}, []string{}, false, "zzz"}
|
||||
p.save(string(defaultPage))
|
||||
defaultPage, _ = ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "templates/privacypolicy.md"))
|
||||
p = WikiData{"privacypolicy", "", []string{}, []string{}, false, "zzz"}
|
||||
p.save(string(defaultPage))
|
||||
|
||||
if len(RuntimeArgs.RestoreDataset) > 0 {
|
||||
fmt.Println("Restoring data from '" + RuntimeArgs.RestoreDataset + "' folder...")
|
||||
filepath.Walk(RuntimeArgs.RestoreDataset, restoreFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
// var q WikiData
|
||||
// q.load("about")
|
||||
// fmt.Println(getImportantVersions(q))
|
||||
|
||||
r := gin.Default()
|
||||
r.LoadHTMLGlob(path.Join(RuntimeArgs.SourcePath, "templates/*"))
|
||||
store := sessions.NewCookieStore([]byte("secret"))
|
||||
r.Use(sessions.Sessions("mysession", store))
|
||||
r.GET("/", newNote)
|
||||
r.HEAD("/", func(c *gin.Context) { c.Status(200) })
|
||||
r.GET("/:title", editNote)
|
||||
r.PUT("/:title", putFile)
|
||||
r.PUT("/", putFile)
|
||||
r.GET("/:title/*option", everythingElse)
|
||||
r.POST("/:title/*option", encryptionRoute)
|
||||
r.DELETE("/listitem", deleteListItem)
|
||||
r.DELETE("/deletepage", deletePage)
|
||||
if RuntimeArgs.ServerCRT != "" && RuntimeArgs.ServerKey != "" {
|
||||
RuntimeArgs.Socket = "wss"
|
||||
fmt.Println("--------------------------")
|
||||
fmt.Println("cowyo (version " + VersionNum + ") is up and running on https://" + RuntimeArgs.ExternalIP)
|
||||
fmt.Println("Admin key: " + RuntimeArgs.AdminKey)
|
||||
fmt.Println("--------------------------")
|
||||
r.RunTLS(RuntimeArgs.Port, RuntimeArgs.ServerCRT, RuntimeArgs.ServerKey)
|
||||
} else {
|
||||
RuntimeArgs.Socket = "ws"
|
||||
if RuntimeArgs.ForceWss {
|
||||
RuntimeArgs.Socket = "wss"
|
||||
bestIP := ""
|
||||
for _, address := range addrs {
|
||||
// check the address type and if it is not a loopback the display it
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String()
|
||||
}
|
||||
}
|
||||
fmt.Println("--------------------------")
|
||||
fmt.Println("cowyo (version " + VersionNum + ") is up and running on http://" + RuntimeArgs.ExternalIP)
|
||||
fmt.Println("Admin key: " + RuntimeArgs.AdminKey)
|
||||
fmt.Println("--------------------------")
|
||||
r.Run(RuntimeArgs.Port)
|
||||
}
|
||||
return bestIP
|
||||
}
|
||||
|
||||
func restoreFile(path string, f os.FileInfo, err error) error {
|
||||
fName := filepath.Base(path)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
fOpen, _ := os.Open(path) // Error handling elided for brevity.
|
||||
io.Copy(buf, fOpen) // Error handling elided for brevity.
|
||||
fOpen.Close()
|
||||
s := string(buf.Bytes())
|
||||
fmt.Println(fName)
|
||||
p := WikiData{fName, "", []string{}, []string{}, false, ""}
|
||||
p.save(s)
|
||||
return nil
|
||||
// exists returns whether the given file or directory exists or not
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func logger(debug bool) *lumber.ConsoleLogger {
|
||||
if !debug {
|
||||
return lumber.NewConsoleLogger(lumber.WARN)
|
||||
}
|
||||
return lumber.NewConsoleLogger(lumber.TRACE)
|
||||
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
import os
|
||||
|
||||
"""DEFUNCT
|
||||
darwin arm
|
||||
darwin arm64
|
||||
dragonfly amd64
|
||||
freebsd 386
|
||||
freebsd amd64
|
||||
freebsd arm
|
||||
linux 386
|
||||
linux arm64
|
||||
linux ppc64le
|
||||
netbsd 386
|
||||
netbsd amd64
|
||||
netbsd arm
|
||||
openbsd 386
|
||||
openbsd amd64
|
||||
openbsd arm
|
||||
plan9 386
|
||||
plan9 amd64
|
||||
solaris amd64
|
||||
windows 386
|
||||
darwin 386
|
||||
darwin amd64
|
||||
linux arm
|
||||
linux ppc64
|
||||
windows amd64"""
|
||||
|
||||
arches = """linux amd64
|
||||
windows amd64
|
||||
linux arm
|
||||
darwin amd64"""
|
||||
|
||||
arches = arches.split("\n")
|
||||
version = "1.0"
|
||||
programName = "awwkoala"
|
||||
try:
|
||||
os.system("rm -rf builds")
|
||||
except:
|
||||
pass
|
||||
os.mkdir("builds")
|
||||
|
||||
for arch in arches:
|
||||
goos = arch.split()[0]
|
||||
goarch = arch.split()[1]
|
||||
exe = ""
|
||||
if "windows" in goos:
|
||||
exe = ".exe"
|
||||
cmd1 = 'env GOOS=%(goos)s GOARCH=%(goarch)s go build -o builds/%(programName)s%(exe)s' % {'goos':goos,'goarch':goarch,'exe':exe,'programName':programName}
|
||||
cmd2 = 'zip -r %(programName)s-%(version)s-%(goos)s-%(goarch)s.zip %(programName)s%(exe)s ../templates ../static' % {'goos':goos,'goarch':goarch,'exe':exe,'version':version,'programName':programName}
|
||||
print(cmd1)
|
||||
os.system(cmd1)
|
||||
os.chdir("builds")
|
||||
print(cmd2)
|
||||
os.system(cmd2)
|
||||
cmd3 = 'rm %(programName)s%(exe)s' % {'exe':exe,'programName':programName}
|
||||
print(cmd3)
|
||||
os.system(cmd3)
|
||||
os.chdir("../")
|
40
multisite_sample.toml
Normal file
40
multisite_sample.toml
Normal file
@ -0,0 +1,40 @@
|
||||
[default]
|
||||
dataDir = "root_data/${HOST}"
|
||||
maxDocumentLength = 100000000
|
||||
|
||||
# Specify multiple times to change keys without expiring sessions
|
||||
[[default.CookieKeys]]
|
||||
authenticateBase64 = "RpW4LjGCPNOx75G8DrywmzlEHLB/ISXCAAayZ47Ifkc="
|
||||
encryptBase64 = "ofCKkrfosQb5T4cvz7R5IMP4BQUDHOPsLSMZZy2CUOA="
|
||||
|
||||
[[sites]]
|
||||
host = "nerdy.party"
|
||||
dataDir = "somewhere else"
|
||||
# theme = "custom.css" # TODO: Theme support. Would prefer to move to a complete directory replacement.
|
||||
defaultPage = "welcome"
|
||||
allowInsecureMarkup = true
|
||||
lock = "1234"
|
||||
debounceSave = 600
|
||||
diary = true
|
||||
accessCode = "correct horse battery staple"
|
||||
fileUploadsAllowed = true
|
||||
maxFileUploadMb = 6
|
||||
port = 8090
|
||||
|
||||
#[sites.TLS]
|
||||
# TODO: ACME support eg letsencrypt
|
||||
#certPath = "path.crt"
|
||||
#keyPath = "path.key"
|
||||
#port = 8443
|
||||
|
||||
[[sites]]
|
||||
host = "cowyo.com"
|
||||
allowInsecureMarkup = false
|
||||
fileUploadsAllowed = false
|
||||
port = 8090
|
||||
|
||||
# Catchall config
|
||||
[[sites]]
|
||||
host = "*"
|
||||
port = 8100
|
||||
cookieSecret = "ASADFGKLJSH+4t4cC2X3f7GzsLZ+wtST67qoLuErpugJz06ZIpdDHEjcMxR+XOLM"
|
@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
var bannedIPs []string
|
||||
|
||||
func init() {
|
||||
go clearBannedIPs()
|
||||
}
|
||||
|
||||
func clearBannedIPs() {
|
||||
for {
|
||||
bannedIPs = []string{}
|
||||
time.Sleep(3 * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
func isIPBanned(ip string) bool {
|
||||
if stringInSlice(ip, bannedIPs) {
|
||||
return true
|
||||
} else {
|
||||
bannedIPs = append(bannedIPs, ip)
|
||||
return false
|
||||
}
|
||||
}
|
633
routes.go
633
routes.go
@ -1,633 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
const _24K = (1 << 20) * 24
|
||||
|
||||
func putFile(c *gin.Context) {
|
||||
if isIPBanned(c.ClientIP()) {
|
||||
c.Data(200, "text/plain", []byte("You are rate limited to 20 requests/hour."))
|
||||
return
|
||||
}
|
||||
filename := c.Param("title")
|
||||
if len(filename) == 0 {
|
||||
filename = randomAlliterateCombo()
|
||||
}
|
||||
contentLength := c.Request.ContentLength
|
||||
var reader io.Reader
|
||||
reader = c.Request.Body
|
||||
if contentLength == -1 {
|
||||
// queue file to disk, because s3 needs content length
|
||||
var err error
|
||||
var f io.Reader
|
||||
|
||||
f = reader
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
n, err := io.CopyN(&b, f, _24K+1)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Printf("%s", err.Error())
|
||||
}
|
||||
|
||||
if n > _24K {
|
||||
file, err := ioutil.TempFile("./", "transfer-")
|
||||
if err != nil {
|
||||
log.Printf("%s", err.Error())
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
n, err = io.Copy(file, io.MultiReader(&b, f))
|
||||
if err != nil {
|
||||
os.Remove(file.Name())
|
||||
log.Printf("%s", err.Error())
|
||||
}
|
||||
|
||||
reader, err = os.Open(file.Name())
|
||||
} else {
|
||||
reader = bytes.NewReader(b.Bytes())
|
||||
}
|
||||
|
||||
contentLength = n
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(reader)
|
||||
// p := WikiData{filename, "", []string{}, []string{}, false, ""}
|
||||
// p.save(buf.String())
|
||||
var p WikiData
|
||||
p.load(strings.ToLower(filename))
|
||||
p.save(buf.String())
|
||||
c.Data(200, "text/plain", []byte("File uploaded to http://"+RuntimeArgs.ExternalIP+"/"+filename))
|
||||
}
|
||||
|
||||
type EncryptionPost struct {
|
||||
Text string `form:"text" json:"text" binding:"required"`
|
||||
Password string `form:"password" json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
func encryptionRoute(c *gin.Context) {
|
||||
title := c.Param("title")
|
||||
option := c.Param("option")
|
||||
var jsonLoad EncryptionPost
|
||||
if option == "/decrypt" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var err error
|
||||
currentText, _, _, _, encrypted, _, _ := getCurrentText(title, -1)
|
||||
if encrypted == true {
|
||||
currentText, err = decryptString(currentText, jsonLoad.Password)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Inorrect passphrase.",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
} else {
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, false, ""}
|
||||
p.save(currentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Could not bind",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/encrypt" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, true, ""}
|
||||
p.save(encryptString(jsonLoad.Text, jsonLoad.Password))
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/lock" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.Locked = jsonLoad.Password
|
||||
p.save(p.CurrentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/unlock" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(p.Locked) > 0 && p.Locked == jsonLoad.Password {
|
||||
p.Locked = ""
|
||||
p.save(p.CurrentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Unlocked!",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Incorrect password!",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func newNote(c *gin.Context) {
|
||||
title := randomAlliterateCombo()
|
||||
c.Redirect(302, "/"+title)
|
||||
}
|
||||
|
||||
func getCodeType(title string) string {
|
||||
if strings.Contains(title, ".js") {
|
||||
return "javascript"
|
||||
} else if strings.Contains(title, ".py") {
|
||||
return "python"
|
||||
} else if strings.Contains(title, ".go") {
|
||||
return "go"
|
||||
} else if strings.Contains(title, ".html") {
|
||||
return "htmlmixed"
|
||||
} else if strings.Contains(title, ".md") {
|
||||
return "markdown"
|
||||
} else if strings.Contains(title, ".sh") {
|
||||
return "shell"
|
||||
} else if strings.Contains(title, ".css") {
|
||||
return "css"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getRecentlyEdited(title string, c *gin.Context) []string {
|
||||
session := sessions.Default(c)
|
||||
var recentlyEdited string
|
||||
v := session.Get("recentlyEdited")
|
||||
editedThings := []string{}
|
||||
if v == nil {
|
||||
recentlyEdited = title
|
||||
} else {
|
||||
editedThings = strings.Split(v.(string), "|||")
|
||||
if !stringInSlice(title, editedThings) {
|
||||
recentlyEdited = v.(string) + "|||" + title
|
||||
} else {
|
||||
recentlyEdited = v.(string)
|
||||
}
|
||||
}
|
||||
session.Set("recentlyEdited", recentlyEdited)
|
||||
session.Save()
|
||||
return editedThings
|
||||
}
|
||||
|
||||
func editNote(c *gin.Context) {
|
||||
title := c.Param("title")
|
||||
if title == "ws" {
|
||||
wshandler(c.Writer, c.Request)
|
||||
} else if title == "robots.txt" {
|
||||
robotsTxtFile, _ := ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "static/text/robots.txt"))
|
||||
c.Data(200, "text/plain", robotsTxtFile)
|
||||
} else if title == "sitemap.xml" {
|
||||
robotsTxtFile, _ := ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "static/text/sitemap.xml"))
|
||||
c.Data(200, "text/plain", robotsTxtFile)
|
||||
} else if strings.ToLower(title) == "help" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||
c.Redirect(302, "/Help/view")
|
||||
} else {
|
||||
version := c.DefaultQuery("version", "-1")
|
||||
versionNum, _ := strconv.Atoi(version)
|
||||
currentText, versions, currentVersion, totalTime, encrypted, locked, currentVersionNum := getCurrentText(title, versionNum)
|
||||
if strings.Contains(c.Request.Header.Get("User-Agent"), "curl/") {
|
||||
c.Data(200, "text/plain", []byte(currentText))
|
||||
return
|
||||
}
|
||||
if encrypted || len(locked) > 0 {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
return
|
||||
}
|
||||
if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
return
|
||||
}
|
||||
numRows := len(strings.Split(currentText, "\n")) + 10
|
||||
totalTimeString := totalTime.String()
|
||||
if totalTime.Seconds() < 1 {
|
||||
totalTimeString = "< 1 s"
|
||||
}
|
||||
splitStrings := strings.Split(title, ".")
|
||||
suffix := splitStrings[len(splitStrings)-1]
|
||||
CodeType := getCodeType(title)
|
||||
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"ExternalIP": RuntimeArgs.ExternalIP,
|
||||
"CurrentText": currentText,
|
||||
"CurrentVersionNum": currentVersionNum,
|
||||
"NumRows": numRows,
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTimeString,
|
||||
"SocketType": RuntimeArgs.Socket,
|
||||
"NoEdit": !currentVersion,
|
||||
"Coding": len(CodeType) > 0,
|
||||
"CodeType": CodeType,
|
||||
"Suffix": suffix,
|
||||
"RecentlyEdited": getRecentlyEdited(title, c),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func everythingElse(c *gin.Context) {
|
||||
option := c.Param("option")
|
||||
title := c.Param("title")
|
||||
if option == "/view" || option == "/v" {
|
||||
version := c.DefaultQuery("version", "-1")
|
||||
noprompt := c.DefaultQuery("noprompt", "-1")
|
||||
versionNum, _ := strconv.Atoi(version)
|
||||
if strings.ToLower(title) == "help" {
|
||||
versionNum = -1
|
||||
}
|
||||
currentText, versions, _, totalTime, encrypted, locked, _ := getCurrentText(title, versionNum)
|
||||
if (strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct")) && strings.ToLower(title) != "help" {
|
||||
currentText = strings.Replace(currentText, "self-destruct\n", `> *This page has been deleted, you cannot return after closing.*`+"\n", 1)
|
||||
currentText = strings.Replace(currentText, "\nself-destruct", "\n"+`> *This page has been deleted, you cannot return after closing.*`, 1)
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, false, ""}
|
||||
p.save("")
|
||||
}
|
||||
|
||||
renderMarkdown(c, currentText, title, versions, "", totalTime, encrypted, noprompt == "-1", len(locked) > 0, getRecentlyEdited(title, c))
|
||||
} else if option == "/raw" {
|
||||
version := c.DefaultQuery("version", "-1")
|
||||
versionNum, _ := strconv.Atoi(version)
|
||||
if strings.ToLower(title) == "help" {
|
||||
versionNum = -1
|
||||
}
|
||||
currentText, _, _, _, _, _, _ := getCurrentText(title, versionNum)
|
||||
c.Writer.Header().Set("Content-Type", contentType(title))
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Data(200, contentType(title), []byte(currentText))
|
||||
} else if title == "ls" && option == "/"+RuntimeArgs.AdminKey && len(RuntimeArgs.AdminKey) > 1 {
|
||||
renderMarkdown(c, listEverything(), "ls", nil, RuntimeArgs.AdminKey, time.Now().Sub(time.Now()), false, false, false, []string{})
|
||||
} else if option == "/list" || option == "/l" {
|
||||
renderList(c, title)
|
||||
} else if title == "static" {
|
||||
serveStaticFile(c, option)
|
||||
} else {
|
||||
c.Redirect(302, "/"+title)
|
||||
}
|
||||
}
|
||||
|
||||
func serveStaticFile(c *gin.Context, option string) {
|
||||
staticFile, err := ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "static") + option)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(404)
|
||||
} else {
|
||||
c.Data(200, contentType(option), []byte(staticFile))
|
||||
}
|
||||
}
|
||||
|
||||
func renderMarkdown(c *gin.Context, currentText string, title string, versions []versionsInfo, AdminKey string, totalTime time.Duration, encrypted bool, noprompt bool, locked bool, recentlyEdited []string) {
|
||||
originalText := currentText
|
||||
CodeType := getCodeType(title)
|
||||
if CodeType == "markdown" {
|
||||
CodeType = ""
|
||||
}
|
||||
r, _ := regexp.Compile("\\[\\[(.*?)\\]\\]")
|
||||
for _, s := range r.FindAllString(currentText, -1) {
|
||||
currentText = strings.Replace(currentText, s, "["+s[2:len(s)-2]+"](/"+s[2:len(s)-2]+"/view)", 1)
|
||||
}
|
||||
unsafe := blackfriday.MarkdownCommon([]byte(currentText))
|
||||
pClean := bluemonday.UGCPolicy()
|
||||
pClean.AllowElements("img")
|
||||
pClean.AllowAttrs("alt").OnElements("img")
|
||||
pClean.AllowAttrs("src").OnElements("img")
|
||||
pClean.AllowAttrs("class").OnElements("a")
|
||||
pClean.AllowAttrs("href").OnElements("a")
|
||||
pClean.AllowAttrs("id").OnElements("a")
|
||||
pClean.AllowDataURIImages()
|
||||
html := pClean.SanitizeBytes(unsafe)
|
||||
html2 := string(html)
|
||||
r, _ = regexp.Compile("\\$\\$(.*?)\\$\\$")
|
||||
for _, s := range r.FindAllString(html2, -1) {
|
||||
html2 = strings.Replace(html2, s, "<span class='texp' data-expr='"+s[2:len(s)-2]+"'></span>", 1)
|
||||
}
|
||||
r, _ = regexp.Compile("\\$(.*?)\\$")
|
||||
for _, s := range r.FindAllString(html2, -1) {
|
||||
html2 = strings.Replace(html2, s, "<span class='texi' data-expr='"+s[1:len(s)-1]+"'></span>", 1)
|
||||
}
|
||||
|
||||
html2 = strings.Replace(html2, "&#36;", "$", -1)
|
||||
html2 = strings.Replace(html2, "&#91;", "[", -1)
|
||||
html2 = strings.Replace(html2, "&#93;", "]", -1)
|
||||
html2 = strings.Replace(html2, "&35;", "#", -1)
|
||||
totalTimeString := totalTime.String()
|
||||
if totalTime.Seconds() < 1 {
|
||||
totalTimeString = "< 1 s"
|
||||
}
|
||||
if encrypted {
|
||||
CodeType = "asciiarmor"
|
||||
}
|
||||
c.HTML(http.StatusOK, "view.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"Body": template.HTML([]byte(html2)),
|
||||
"CurrentText": originalText,
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTimeString,
|
||||
"AdminKey": AdminKey,
|
||||
"Encrypted": encrypted,
|
||||
"Locked": locked,
|
||||
"Prompt": noprompt,
|
||||
"LockedOrEncrypted": locked || encrypted,
|
||||
"Coding": len(CodeType) > 0,
|
||||
"CodeType": CodeType,
|
||||
"RecentlyEdited": recentlyEdited,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func reorderList(text string) ([]template.HTML, []string) {
|
||||
listItemsString := ""
|
||||
for _, lineString := range strings.Split(text, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if string(lineString[0]) != "-" {
|
||||
listItemsString += "- " + lineString + "\n"
|
||||
} else {
|
||||
listItemsString += lineString + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of template.HTML for rendering
|
||||
renderedListString := string(blackfriday.MarkdownCommon([]byte(listItemsString)))
|
||||
listItems := []template.HTML{}
|
||||
endItems := []template.HTML{}
|
||||
for _, lineString := range strings.Split(renderedListString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "<del>") || strings.Contains(lineString, "</ul>") {
|
||||
endItems = append(endItems, template.HTML(lineString))
|
||||
} else {
|
||||
listItems = append(listItems, template.HTML(lineString))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of strings for deleting
|
||||
listItemsStringArray := []string{}
|
||||
endItemsStringArray := []string{}
|
||||
for _, lineString := range strings.Split(listItemsString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "~~") {
|
||||
endItemsStringArray = append(endItemsStringArray, lineString)
|
||||
} else {
|
||||
listItemsStringArray = append(listItemsStringArray, lineString)
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(listItems, endItems...), append(listItemsStringArray, endItemsStringArray...)
|
||||
}
|
||||
|
||||
func renderList(c *gin.Context, title string) {
|
||||
if strings.ToLower(title) == "help" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||
c.Redirect(302, "/Help/view")
|
||||
}
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
currentText := p.CurrentText
|
||||
if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
if p.Encrypted || len(p.Locked) > 0 {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
|
||||
pClean := bluemonday.UGCPolicy()
|
||||
pClean.AllowElements("img")
|
||||
pClean.AllowAttrs("alt").OnElements("img")
|
||||
pClean.AllowAttrs("src").OnElements("img")
|
||||
pClean.AllowAttrs("class").OnElements("a")
|
||||
pClean.AllowAttrs("href").OnElements("a")
|
||||
pClean.AllowAttrs("id").OnElements("a")
|
||||
pClean.AllowDataURIImages()
|
||||
text := pClean.SanitizeBytes([]byte(p.CurrentText))
|
||||
listItems, _ := reorderList(string(text))
|
||||
for i := range listItems {
|
||||
newHTML := strings.Replace(string(listItems[i]), "</a>", "</a>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "<a href=", "</span><a href=", -1)
|
||||
newHTML = strings.Replace(newHTML, "<li>", "<li>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "</li>", "</span></li>", -1)
|
||||
newHTML = strings.Replace(newHTML, "<li>"+`<span id="`+strconv.Itoa(i)+`" class="deletable"><del>`, "<li><del>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "</del></span></li>", "</span></del></li>", -1)
|
||||
listItems[i] = template.HTML([]byte(newHTML))
|
||||
}
|
||||
c.HTML(http.StatusOK, "list.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"ListItems": listItems,
|
||||
"RecentlyEdited": getRecentlyEdited(title, c),
|
||||
})
|
||||
}
|
||||
|
||||
func deleteListItem(c *gin.Context) {
|
||||
lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
|
||||
title := c.Query("title") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||
if err == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, listItems := reorderList(p.CurrentText)
|
||||
newText := p.CurrentText
|
||||
for i, lineString := range listItems {
|
||||
// fmt.Println(i, lineString, lineNum)
|
||||
if i+1 == lineNum {
|
||||
// fmt.Println("MATCHED")
|
||||
if strings.Contains(lineString, "~~") == false {
|
||||
// fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
|
||||
} else {
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
|
||||
}
|
||||
p.save(newText)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"message": "Done.",
|
||||
})
|
||||
} else {
|
||||
c.JSON(404, gin.H{
|
||||
"message": "?",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func deletePage(c *gin.Context) {
|
||||
deleteName := c.DefaultQuery("DeleteName", "None")
|
||||
// if adminKey == RuntimeArgs.AdminKey || true == true {
|
||||
if strings.ToLower(deleteName) != "help" {
|
||||
p := WikiData{strings.ToLower(deleteName), "", []string{}, []string{}, false, ""}
|
||||
p.save("")
|
||||
}
|
||||
// // remove from program data
|
||||
// var deleteKey []byte
|
||||
// foundKey := false
|
||||
// err := db.View(func(tx *bolt.Tx) error {
|
||||
// b := tx.Bucket([]byte("programdata"))
|
||||
// c := b.Cursor()
|
||||
// for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
// if strings.ToLower(string(v)) == strings.ToLower(deleteName) {
|
||||
// fmt.Println("FOUND " + string(v))
|
||||
// deleteKey = k
|
||||
// foundKey = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// })
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// if foundKey == true {
|
||||
// fmt.Println(len([]string{}))
|
||||
// fmt.Println(deleteKey)
|
||||
// db.View(func(tx *bolt.Tx) error {
|
||||
// b := tx.Bucket([]byte("programdata"))
|
||||
// err := b.Delete(deleteKey)
|
||||
// return err
|
||||
// })
|
||||
// }
|
||||
|
||||
// return OKAY
|
||||
c.JSON(200, gin.H{
|
||||
"message": "Done.",
|
||||
})
|
||||
// } else {
|
||||
// c.JSON(404, gin.H{
|
||||
// "message": "?",
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
func listEverything() string {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
everything := `| Title | Current size | Changes | Total Size | |
|
||||
| --------- |-------------| -----| ------------- | ------------- |
|
||||
`
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
// Assume bucket exists and has keys
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
var p WikiData
|
||||
p.load(string(k))
|
||||
if len(p.CurrentText) > 1 {
|
||||
contentSize := strconv.Itoa(len(p.CurrentText))
|
||||
numChanges := strconv.Itoa(len(p.Diffs))
|
||||
totalSize := strconv.Itoa(len(v))
|
||||
everything += "| [" + p.Title + "](/" + p.Title + "/view) | " + contentSize + " | " + numChanges + " | " + totalSize + ` | <a class="deleteable" id="` + p.Title + `">Delete</a> | ` + "\n"
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return everything
|
||||
}
|
||||
|
||||
func dumpEverything(folderpath string) {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
err := os.MkdirAll(folderpath, 0777)
|
||||
if err != nil {
|
||||
fmt.Printf("%s folder already exists.", folderpath)
|
||||
}
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
// Assume bucket exists and has keys
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
c := b.Cursor()
|
||||
for k, _ := c.First(); k != nil; k, _ = c.Next() {
|
||||
var p WikiData
|
||||
p.load(string(k))
|
||||
if len(p.CurrentText) > 0 {
|
||||
ioutil.WriteFile(path.Join(folderpath, string(k)), []byte(p.CurrentText), 0644)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
1174
server/bindata-debug.go
Normal file
1174
server/bindata-debug.go
Normal file
File diff suppressed because it is too large
Load Diff
1309
server/bindata.go
Normal file
1309
server/bindata.go
Normal file
File diff suppressed because one or more lines are too long
10
server/debug.go
Normal file
10
server/debug.go
Normal file
@ -0,0 +1,10 @@
|
||||
// +build debug
|
||||
|
||||
package server
|
||||
|
||||
import "github.com/jcelliott/lumber"
|
||||
|
||||
func init() {
|
||||
hotTemplateReloading = true
|
||||
LogLevel = lumber.TRACE
|
||||
}
|
889
server/handlers.go
Executable file
889
server/handlers.go
Executable file
@ -0,0 +1,889 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
secretRequired "github.com/danielheath/gin-teeny-security"
|
||||
"github.com/gin-contrib/multitemplate"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jcelliott/lumber"
|
||||
"github.com/schollz/cowyo/encrypt"
|
||||
)
|
||||
|
||||
const minutesToUnlock = 10.0
|
||||
|
||||
type Site struct {
|
||||
PathToData string
|
||||
Css []byte
|
||||
DefaultPage string
|
||||
DefaultPassword string
|
||||
Debounce int
|
||||
Diary bool
|
||||
SessionStore cookie.Store
|
||||
SecretCode string
|
||||
AllowInsecure bool
|
||||
Fileuploads bool
|
||||
MaxUploadSize uint
|
||||
Logger *lumber.ConsoleLogger
|
||||
MaxDocumentSize uint // in runes; about a 10mb limit by default
|
||||
saveMut sync.Mutex
|
||||
sitemapUpToDate bool // TODO this makes everything use a pointer
|
||||
}
|
||||
|
||||
func (s *Site) defaultLock() string {
|
||||
if s.DefaultPassword == "" {
|
||||
return ""
|
||||
}
|
||||
return HashPassword(s.DefaultPassword)
|
||||
}
|
||||
|
||||
var hotTemplateReloading bool
|
||||
var LogLevel int = lumber.WARN
|
||||
|
||||
func Serve(
|
||||
filepathToData,
|
||||
host,
|
||||
port,
|
||||
crt_path,
|
||||
key_path string,
|
||||
TLS bool,
|
||||
cssFile string,
|
||||
defaultPage string,
|
||||
defaultPassword string,
|
||||
debounce int,
|
||||
diary bool,
|
||||
secret string,
|
||||
secretCode string,
|
||||
allowInsecure bool,
|
||||
fileuploads bool,
|
||||
maxUploadSize uint,
|
||||
maxDocumentSize uint,
|
||||
logger *lumber.ConsoleLogger,
|
||||
) {
|
||||
var customCSS []byte
|
||||
// collect custom CSS
|
||||
if len(cssFile) > 0 {
|
||||
var errRead error
|
||||
customCSS, errRead = ioutil.ReadFile(cssFile)
|
||||
if errRead != nil {
|
||||
fmt.Println(errRead)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Loaded CSS file, %d bytes\n", len(customCSS))
|
||||
}
|
||||
|
||||
router := Site{
|
||||
PathToData: filepathToData,
|
||||
Css: customCSS,
|
||||
DefaultPage: defaultPage,
|
||||
DefaultPassword: defaultPassword,
|
||||
Debounce: debounce,
|
||||
Diary: diary,
|
||||
SessionStore: cookie.NewStore([]byte(secret)),
|
||||
SecretCode: secretCode,
|
||||
AllowInsecure: allowInsecure,
|
||||
Fileuploads: fileuploads,
|
||||
MaxUploadSize: maxUploadSize,
|
||||
Logger: logger,
|
||||
MaxDocumentSize: maxDocumentSize,
|
||||
}.Router()
|
||||
|
||||
if TLS {
|
||||
http.ListenAndServeTLS(host+":"+port, crt_path, key_path, router)
|
||||
} else {
|
||||
panic(router.Run(host + ":" + port))
|
||||
}
|
||||
}
|
||||
|
||||
func (s Site) Router() *gin.Engine {
|
||||
if s.Logger == nil {
|
||||
s.Logger = lumber.NewConsoleLogger(lumber.TRACE)
|
||||
}
|
||||
|
||||
if hotTemplateReloading {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
router := gin.Default()
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"sniffContentType": s.sniffContentType,
|
||||
})
|
||||
|
||||
if hotTemplateReloading {
|
||||
router.LoadHTMLGlob("templates/*.tmpl")
|
||||
} else {
|
||||
router.HTMLRender = s.loadTemplates("index.tmpl")
|
||||
}
|
||||
|
||||
router.Use(sessions.Sessions("_session", s.SessionStore))
|
||||
if s.SecretCode != "" {
|
||||
cfg := &secretRequired.Config{
|
||||
Secret: s.SecretCode,
|
||||
Path: "/login/",
|
||||
RequireAuth: func(c *gin.Context) bool {
|
||||
page := c.Param("page")
|
||||
cmd := c.Param("command")
|
||||
|
||||
if page == "sitemap.xml" || page == "favicon.ico" || page == "static" || page == "uploads" {
|
||||
return false // no auth for these
|
||||
}
|
||||
|
||||
if page != "" && cmd == "/read" {
|
||||
p := s.Open(page)
|
||||
if p != nil && p.IsPublished {
|
||||
return false // Published pages don't require auth.
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
}
|
||||
router.Use(cfg.Middleware)
|
||||
}
|
||||
|
||||
// router.Use(static.Serve("/static/", static.LocalFile("./static", true)))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
if s.DefaultPage != "" {
|
||||
c.Redirect(302, "/"+s.DefaultPage+"/read")
|
||||
} else {
|
||||
c.Redirect(302, "/"+randomAlliterateCombo())
|
||||
}
|
||||
})
|
||||
|
||||
router.POST("/uploads", s.handleUpload)
|
||||
|
||||
router.GET("/:page", func(c *gin.Context) {
|
||||
page := c.Param("page")
|
||||
c.Redirect(302, "/"+page+"/")
|
||||
})
|
||||
router.GET("/:page/*command", s.handlePageRequest)
|
||||
router.POST("/update", s.handlePageUpdate)
|
||||
router.POST("/relinquish", s.handlePageRelinquish) // relinquish returns the page no matter what (and destroys if nessecary)
|
||||
router.POST("/exists", s.handlePageExists)
|
||||
router.POST("/prime", s.handlePrime)
|
||||
router.POST("/lock", s.handleLock)
|
||||
router.POST("/publish", s.handlePublish)
|
||||
router.POST("/encrypt", s.handleEncrypt)
|
||||
router.DELETE("/oldlist", s.handleClearOldListItems)
|
||||
router.DELETE("/listitem", s.deleteListItem)
|
||||
|
||||
// start long-processes as threads
|
||||
go s.thread_SiteMap()
|
||||
|
||||
// Allow iframe/scripts in markup?
|
||||
allowInsecureHtml = s.AllowInsecure
|
||||
return router
|
||||
}
|
||||
|
||||
func (s *Site) loadTemplates(list ...string) multitemplate.Render {
|
||||
r := multitemplate.New()
|
||||
|
||||
for _, x := range list {
|
||||
templateString, err := Asset("templates/" + x)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tmplMessage, err := template.New(x).Funcs(template.FuncMap{
|
||||
"sniffContentType": s.sniffContentType,
|
||||
}).Parse(string(templateString))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r.Add(x, tmplMessage)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func pageIsLocked(p *Page, c *gin.Context) bool {
|
||||
// it is easier to reason about when the page is actually unlocked
|
||||
var unlocked = !p.IsLocked ||
|
||||
(p.IsLocked && p.UnlockedFor == getSetSessionID(c))
|
||||
return !unlocked
|
||||
}
|
||||
|
||||
func (s *Site) handlePageRelinquish(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
}
|
||||
var json QueryJSON
|
||||
err := c.BindJSON(&json)
|
||||
if err != nil {
|
||||
s.Logger.Trace(err.Error())
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
|
||||
return
|
||||
}
|
||||
if len(json.Page) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"})
|
||||
return
|
||||
}
|
||||
message := "Relinquished"
|
||||
p := s.Open(json.Page)
|
||||
name := p.Meta
|
||||
if name == "" {
|
||||
name = json.Page
|
||||
}
|
||||
text := p.Text.GetCurrent()
|
||||
isLocked := pageIsLocked(p, c)
|
||||
isEncrypted := p.IsEncrypted
|
||||
destroyed := p.IsPrimedForSelfDestruct
|
||||
if !isLocked && p.IsPrimedForSelfDestruct {
|
||||
p.Erase()
|
||||
message = "Relinquished and erased"
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true,
|
||||
"name": name,
|
||||
"message": message,
|
||||
"text": text,
|
||||
"locked": isLocked,
|
||||
"encrypted": isEncrypted,
|
||||
"destroyed": destroyed})
|
||||
}
|
||||
|
||||
func getSetSessionID(c *gin.Context) (sid string) {
|
||||
var (
|
||||
session = sessions.Default(c)
|
||||
v = session.Get("sid")
|
||||
)
|
||||
if v != nil {
|
||||
sid = v.(string)
|
||||
}
|
||||
if v == nil || sid == "" {
|
||||
sid = RandStringBytesMaskImprSrc(8)
|
||||
session.Set("sid", sid)
|
||||
session.Save()
|
||||
}
|
||||
return sid
|
||||
}
|
||||
|
||||
func (s *Site) thread_SiteMap() {
|
||||
for {
|
||||
if !s.sitemapUpToDate {
|
||||
s.Logger.Info("Generating sitemap...")
|
||||
s.sitemapUpToDate = true
|
||||
ioutil.WriteFile(path.Join(s.PathToData, "sitemap.xml"), []byte(s.generateSiteMap()), 0644)
|
||||
s.Logger.Info("..finished generating sitemap")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) generateSiteMap() (sitemap string) {
|
||||
files, _ := ioutil.ReadDir(s.PathToData)
|
||||
lastEdited := make([]string, len(files))
|
||||
names := make([]string, len(files))
|
||||
i := 0
|
||||
for _, f := range files {
|
||||
names[i] = DecodeFileName(f.Name())
|
||||
p := s.Open(names[i])
|
||||
if p.IsPublished {
|
||||
lastEdited[i] = time.Unix(p.Text.LastEditTime()/1000000000, 0).Format("2006-01-02")
|
||||
i++
|
||||
}
|
||||
}
|
||||
names = names[:i]
|
||||
lastEdited = lastEdited[:i]
|
||||
sitemap = ""
|
||||
for i := range names {
|
||||
sitemap += fmt.Sprintf(`
|
||||
<url>
|
||||
<loc>{{ .Request.Host }}/%s/read</loc>
|
||||
<lastmod>%s</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
`, names[i], lastEdited[i])
|
||||
}
|
||||
sitemap += "</urlset>"
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Site) handlePageRequest(c *gin.Context) {
|
||||
page := c.Param("page")
|
||||
command := c.Param("command")
|
||||
|
||||
if page == "sitemap.xml" {
|
||||
siteMap, err := ioutil.ReadFile(path.Join(s.PathToData, "sitemap.xml"))
|
||||
if err != nil {
|
||||
c.Data(http.StatusInternalServerError, contentType("sitemap.xml"), []byte(""))
|
||||
} else {
|
||||
fmt.Fprintln(c.Writer, `<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`)
|
||||
template.Must(template.New("sitemap").Parse(string(siteMap))).Execute(c.Writer, c)
|
||||
}
|
||||
return
|
||||
} else if page == "favicon.ico" {
|
||||
data, _ := Asset("/static/img/cowyo/favicon.ico")
|
||||
c.Data(http.StatusOK, contentType("/static/img/cowyo/favicon.ico"), data)
|
||||
return
|
||||
} else if page == "static" {
|
||||
filename := page + command
|
||||
var data []byte
|
||||
if filename == "static/css/custom.css" {
|
||||
data = s.Css
|
||||
} else {
|
||||
var errAssset error
|
||||
data, errAssset = Asset(filename)
|
||||
if errAssset != nil {
|
||||
c.String(http.StatusInternalServerError, "Could not find data")
|
||||
}
|
||||
}
|
||||
c.Data(http.StatusOK, contentType(filename), data)
|
||||
return
|
||||
} else if page == "uploads" {
|
||||
if len(command) == 0 || command == "/" || command == "/edit" {
|
||||
if !s.Fileuploads {
|
||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
command = command[1:]
|
||||
if !strings.HasSuffix(command, ".upload") {
|
||||
command = command + ".upload"
|
||||
}
|
||||
pathname := path.Join(s.PathToData, command)
|
||||
|
||||
if allowInsecureHtml {
|
||||
c.Header(
|
||||
"Content-Disposition",
|
||||
`inline; filename="`+c.DefaultQuery("filename", "upload")+`"`,
|
||||
)
|
||||
} else {
|
||||
// Prevent malicious html uploads by forcing type to plaintext and 'download-instead-of-view'
|
||||
c.Header("Content-Type", "text/plain")
|
||||
c.Header(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="`+c.DefaultQuery("filename", "upload")+`"`,
|
||||
)
|
||||
}
|
||||
c.File(pathname)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
p := s.Open(page)
|
||||
if len(command) < 2 {
|
||||
if p.IsPublished {
|
||||
c.Redirect(302, "/"+page+"/read")
|
||||
} else {
|
||||
c.Redirect(302, "/"+page+"/edit")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// use the default lock
|
||||
if s.defaultLock() != "" && p.IsNew() {
|
||||
p.IsLocked = true
|
||||
p.PassphraseToUnlock = s.defaultLock()
|
||||
}
|
||||
|
||||
version := c.DefaultQuery("version", "ajksldfjl")
|
||||
isLocked := pageIsLocked(p, c)
|
||||
|
||||
// Disallow anything but viewing locked/encrypted pages
|
||||
if (p.IsEncrypted || isLocked) &&
|
||||
(command[0:2] != "/v" && command[0:2] != "/r") {
|
||||
c.Redirect(302, "/"+page+"/view")
|
||||
return
|
||||
}
|
||||
|
||||
// Destroy page if it is opened and primed
|
||||
if p.IsPrimedForSelfDestruct && !isLocked && !p.IsEncrypted {
|
||||
p.Update("<center><em>This page has self-destructed. You cannot return to it.</em></center>\n\n" + p.Text.GetCurrent())
|
||||
p.Erase()
|
||||
if p.IsPublished {
|
||||
command = "/read"
|
||||
} else {
|
||||
command = "/view"
|
||||
}
|
||||
}
|
||||
if command == "/erase" {
|
||||
if !isLocked && !p.IsEncrypted {
|
||||
p.Erase()
|
||||
c.Redirect(302, "/"+page+"/edit")
|
||||
} else {
|
||||
c.Redirect(302, "/"+page+"/view")
|
||||
}
|
||||
return
|
||||
}
|
||||
rawText := p.Text.GetCurrent()
|
||||
rawHTML := p.RenderedPage
|
||||
|
||||
// Check to see if an old version is requested
|
||||
versionInt, versionErr := strconv.Atoi(version)
|
||||
if versionErr == nil && versionInt > 0 {
|
||||
versionText, err := p.Text.GetPreviousByTimestamp(int64(versionInt))
|
||||
if err == nil {
|
||||
rawText = versionText
|
||||
rawHTML = GithubMarkdownToHTML(rawText)
|
||||
}
|
||||
}
|
||||
|
||||
// Get history
|
||||
var versionsInt64 []int64
|
||||
var versionsChangeSums []int
|
||||
var versionsText []string
|
||||
if command[0:2] == "/h" {
|
||||
versionsInt64, versionsChangeSums = p.Text.GetMajorSnapshotsAndChangeSums(60) // get snapshots 60 seconds apart
|
||||
versionsText = make([]string, len(versionsInt64))
|
||||
for i, v := range versionsInt64 {
|
||||
versionsText[i] = time.Unix(v/1000000000, 0).Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
}
|
||||
versionsText = reverseSliceString(versionsText)
|
||||
versionsInt64 = reverseSliceInt64(versionsInt64)
|
||||
versionsChangeSums = reverseSliceInt(versionsChangeSums)
|
||||
}
|
||||
|
||||
if len(command) > 3 && command[0:3] == "/ra" {
|
||||
c.Writer.Header().Set("Content-Type", "text/plain")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Data(200, contentType(p.Name), []byte(rawText))
|
||||
return
|
||||
}
|
||||
|
||||
var DirectoryEntries []os.FileInfo
|
||||
if page == "ls" {
|
||||
command = "/view"
|
||||
DirectoryEntries = s.DirectoryList()
|
||||
}
|
||||
if page == "uploads" {
|
||||
command = "/view"
|
||||
var err error
|
||||
DirectoryEntries, err = s.UploadList()
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// swap out /view for /read if it is published
|
||||
if p.IsPublished {
|
||||
rawHTML = strings.Replace(rawHTML, "/view", "/read", -1)
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"EditPage": command[0:2] == "/e", // /edit
|
||||
"ViewPage": command[0:2] == "/v", // /view
|
||||
"ListPage": command[0:2] == "/l", // /list
|
||||
"HistoryPage": command[0:2] == "/h", // /history
|
||||
"ReadPage": command[0:2] == "/r", // /history
|
||||
"DontKnowPage": command[0:2] != "/e" &&
|
||||
command[0:2] != "/v" &&
|
||||
command[0:2] != "/l" &&
|
||||
command[0:2] != "/r" &&
|
||||
command[0:2] != "/h",
|
||||
"DirectoryPage": page == "ls" || page == "uploads",
|
||||
"UploadPage": page == "uploads",
|
||||
"DirectoryEntries": DirectoryEntries,
|
||||
"Page": page,
|
||||
"RenderedPage": template.HTML([]byte(rawHTML)),
|
||||
"RawPage": rawText,
|
||||
"Versions": versionsInt64,
|
||||
"VersionsText": versionsText,
|
||||
"VersionsChangeSums": versionsChangeSums,
|
||||
"IsLocked": isLocked,
|
||||
"IsEncrypted": p.IsEncrypted,
|
||||
"ListItems": renderList(rawText),
|
||||
"Route": "/" + page + command,
|
||||
"HasDotInName": strings.Contains(page, "."),
|
||||
"RecentlyEdited": getRecentlyEdited(page, c),
|
||||
"IsPublished": p.IsPublished,
|
||||
"CustomCSS": len(s.Css) > 0,
|
||||
"Debounce": s.Debounce,
|
||||
"DiaryMode": s.Diary,
|
||||
"Date": time.Now().Format("2006-01-02"),
|
||||
"UnixTime": time.Now().Unix(),
|
||||
"ChildPageNames": p.ChildPageNames(),
|
||||
"AllowFileUploads": s.Fileuploads,
|
||||
"MaxUploadMB": s.MaxUploadSize,
|
||||
})
|
||||
}
|
||||
|
||||
func getRecentlyEdited(title string, c *gin.Context) []string {
|
||||
session := sessions.Default(c)
|
||||
var recentlyEdited string
|
||||
v := session.Get("recentlyEdited")
|
||||
editedThings := []string{}
|
||||
if v == nil {
|
||||
recentlyEdited = title
|
||||
} else {
|
||||
editedThings = strings.Split(v.(string), "|||")
|
||||
if !stringInSlice(title, editedThings) {
|
||||
recentlyEdited = v.(string) + "|||" + title
|
||||
} else {
|
||||
recentlyEdited = v.(string)
|
||||
}
|
||||
}
|
||||
session.Set("recentlyEdited", recentlyEdited)
|
||||
session.Save()
|
||||
editedThingsWithoutCurrent := make([]string, len(editedThings))
|
||||
i := 0
|
||||
for _, thing := range editedThings {
|
||||
if thing == title {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(thing, "icon-") {
|
||||
continue
|
||||
}
|
||||
editedThingsWithoutCurrent[i] = thing
|
||||
i++
|
||||
}
|
||||
return editedThingsWithoutCurrent[:i]
|
||||
}
|
||||
|
||||
func (s *Site) handlePageExists(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
}
|
||||
var json QueryJSON
|
||||
err := c.BindJSON(&json)
|
||||
if err != nil {
|
||||
s.Logger.Trace(err.Error())
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON", "exists": false})
|
||||
return
|
||||
}
|
||||
p := s.Open(json.Page)
|
||||
if len(p.Text.GetCurrent()) > 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " found", "exists": true})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": json.Page + " not found", "exists": false})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *Site) handlePageUpdate(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
NewText string `json:"new_text"`
|
||||
FetchedAt int64 `json:"fetched_at"`
|
||||
IsEncrypted bool `json:"is_encrypted"`
|
||||
IsPrimed bool `json:"is_primed"`
|
||||
Meta string `json:"meta"`
|
||||
}
|
||||
var json QueryJSON
|
||||
err := c.BindJSON(&json)
|
||||
if err != nil {
|
||||
s.Logger.Trace(err.Error())
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
|
||||
return
|
||||
}
|
||||
if uint(len(json.NewText)) > s.MaxDocumentSize {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Too much"})
|
||||
return
|
||||
}
|
||||
if len(json.Page) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Must specify `page`"})
|
||||
return
|
||||
}
|
||||
s.Logger.Trace("Update: %v", json)
|
||||
p := s.Open(json.Page)
|
||||
var (
|
||||
message string
|
||||
sinceLastEdit = time.Since(p.LastEditTime())
|
||||
)
|
||||
success := false
|
||||
if pageIsLocked(p, c) {
|
||||
if sinceLastEdit < minutesToUnlock {
|
||||
message = "This page is being edited by someone else"
|
||||
} else {
|
||||
// here what might have happened is that two people unlock without
|
||||
// editing thus they both suceeds but only one is able to edit
|
||||
message = "Locked, must unlock first"
|
||||
}
|
||||
} else if p.IsEncrypted {
|
||||
message = "Encrypted, must decrypt first"
|
||||
} else if json.FetchedAt > 0 && p.LastEditUnixTime() > json.FetchedAt {
|
||||
message = "Refusing to overwrite others work"
|
||||
} else {
|
||||
p.Meta = json.Meta
|
||||
p.Update(json.NewText)
|
||||
if json.IsEncrypted {
|
||||
p.IsEncrypted = true
|
||||
}
|
||||
if json.IsPrimed {
|
||||
p.IsPrimedForSelfDestruct = true
|
||||
}
|
||||
p.Save()
|
||||
message = "Saved"
|
||||
if p.IsPublished {
|
||||
s.sitemapUpToDate = false
|
||||
}
|
||||
success = true
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": success, "message": message, "unix_time": time.Now().Unix()})
|
||||
}
|
||||
|
||||
func (s *Site) handlePrime(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
}
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
s.Logger.Trace("Update: %v", json)
|
||||
p := s.Open(json.Page)
|
||||
if pageIsLocked(p, c) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
|
||||
return
|
||||
} else if p.IsEncrypted {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
|
||||
return
|
||||
}
|
||||
p.IsPrimedForSelfDestruct = true
|
||||
p.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"})
|
||||
}
|
||||
|
||||
func (s *Site) handleLock(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
Passphrase string `json:"passphrase"`
|
||||
}
|
||||
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
p := s.Open(json.Page)
|
||||
if s.defaultLock() != "" && p.IsNew() {
|
||||
p.IsLocked = true // IsLocked was replaced by variable wrt Context
|
||||
p.PassphraseToUnlock = s.defaultLock()
|
||||
}
|
||||
|
||||
if p.IsEncrypted {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
|
||||
return
|
||||
}
|
||||
var (
|
||||
message string
|
||||
sessionID = getSetSessionID(c)
|
||||
sinceLastEdit = time.Since(p.LastEditTime())
|
||||
)
|
||||
|
||||
// both lock/unlock ends here on locked&timeout combination
|
||||
if p.IsLocked &&
|
||||
p.UnlockedFor != sessionID &&
|
||||
p.UnlockedFor != "" &&
|
||||
sinceLastEdit.Minutes() < minutesToUnlock {
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": fmt.Sprintf("This page is being edited by someone else! Will unlock automatically %2.0f minutes after the last change.", minutesToUnlock-sinceLastEdit.Minutes()),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !pageIsLocked(p, c) {
|
||||
p.IsLocked = true
|
||||
p.PassphraseToUnlock = HashPassword(json.Passphrase)
|
||||
p.UnlockedFor = ""
|
||||
message = "Locked"
|
||||
} else {
|
||||
err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
|
||||
if err2 != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
|
||||
return
|
||||
}
|
||||
p.UnlockedFor = sessionID
|
||||
message = "Unlocked only for you"
|
||||
}
|
||||
p.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
|
||||
}
|
||||
|
||||
func (s *Site) handlePublish(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
Publish bool `json:"publish"`
|
||||
}
|
||||
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
p := s.Open(json.Page)
|
||||
p.IsPublished = json.Publish
|
||||
p.Save()
|
||||
message := "Published"
|
||||
if !p.IsPublished {
|
||||
message = "Unpublished"
|
||||
}
|
||||
s.sitemapUpToDate = false
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
|
||||
}
|
||||
|
||||
func (s *Site) handleUpload(c *gin.Context) {
|
||||
if !s.Fileuploads {
|
||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Uploads are disabled on this server"))
|
||||
return
|
||||
}
|
||||
|
||||
file, info, err := c.Request.FormFile("file")
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, file); err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
newName := "sha256-" + encodeBytesToBase32(h.Sum(nil))
|
||||
|
||||
// Replaces any existing version, but sha256 collisions are rare as anything.
|
||||
outfile, err := os.Create(path.Join(s.PathToData, newName+".upload"))
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
file.Seek(0, io.SeekStart)
|
||||
_, err = io.Copy(outfile, file)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Location", "/uploads/"+newName+"?filename="+url.QueryEscape(info.Filename))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Site) handleEncrypt(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
Passphrase string `json:"passphrase"`
|
||||
}
|
||||
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
p := s.Open(json.Page)
|
||||
if pageIsLocked(p, c) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
|
||||
return
|
||||
}
|
||||
q := s.Open(json.Page)
|
||||
var message string
|
||||
if p.IsEncrypted {
|
||||
decrypted, err2 := encrypt.DecryptString(p.Text.GetCurrent(), json.Passphrase)
|
||||
if err2 != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong password"})
|
||||
return
|
||||
}
|
||||
q.Erase()
|
||||
q = s.Open(json.Page)
|
||||
q.Update(decrypted)
|
||||
q.IsEncrypted = false
|
||||
q.IsLocked = p.IsLocked
|
||||
q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
|
||||
message = "Decrypted"
|
||||
} else {
|
||||
currentText := p.Text.GetCurrent()
|
||||
encrypted, _ := encrypt.EncryptString(currentText, json.Passphrase)
|
||||
q.Erase()
|
||||
q = s.Open(json.Page)
|
||||
q.Update(encrypted)
|
||||
q.IsEncrypted = true
|
||||
q.IsLocked = p.IsLocked
|
||||
q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
|
||||
message = "Encrypted"
|
||||
}
|
||||
q.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
|
||||
}
|
||||
|
||||
func (s *Site) deleteListItem(c *gin.Context) {
|
||||
lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
|
||||
page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||
if err == nil {
|
||||
p := s.Open(page)
|
||||
|
||||
_, listItems := reorderList(p.Text.GetCurrent())
|
||||
newText := p.Text.GetCurrent()
|
||||
for i, lineString := range listItems {
|
||||
// fmt.Println(i, lineString, lineNum)
|
||||
if i+1 == lineNum {
|
||||
// fmt.Println("MATCHED")
|
||||
if strings.Contains(lineString, "~~") == false {
|
||||
// fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
|
||||
} else {
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
|
||||
}
|
||||
p.Update(newText)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"success": true,
|
||||
"message": "Done.",
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) handleClearOldListItems(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
}
|
||||
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
p := s.Open(json.Page)
|
||||
if p.IsEncrypted {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
|
||||
return
|
||||
}
|
||||
if pageIsLocked(p, c) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
|
||||
return
|
||||
}
|
||||
lines := strings.Split(p.Text.GetCurrent(), "\n")
|
||||
newLines := make([]string, len(lines))
|
||||
newLinesI := 0
|
||||
for _, line := range lines {
|
||||
if strings.Count(line, "~~") != 2 {
|
||||
newLines[newLinesI] = line
|
||||
newLinesI++
|
||||
}
|
||||
}
|
||||
p.Update(strings.Join(newLines[0:newLinesI], "\n"))
|
||||
p.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Cleared"})
|
||||
}
|
62
server/listify.go
Normal file
62
server/listify.go
Normal file
@ -0,0 +1,62 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func reorderList(text string) ([]template.HTML, []string) {
|
||||
listItemsString := ""
|
||||
for _, lineString := range strings.Split(text, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if string(lineString[0]) != "-" {
|
||||
listItemsString += "- " + lineString + "\n"
|
||||
} else {
|
||||
listItemsString += lineString + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of template.HTML for rendering
|
||||
renderedListString := MarkdownToHtml(listItemsString)
|
||||
listItems := []template.HTML{}
|
||||
endItems := []template.HTML{}
|
||||
for _, lineString := range strings.Split(renderedListString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "<del>") || strings.Contains(lineString, "</ul>") {
|
||||
endItems = append(endItems, template.HTML(lineString))
|
||||
} else {
|
||||
listItems = append(listItems, template.HTML(lineString))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of strings for deleting
|
||||
listItemsStringArray := []string{}
|
||||
endItemsStringArray := []string{}
|
||||
for _, lineString := range strings.Split(listItemsString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "~~") {
|
||||
endItemsStringArray = append(endItemsStringArray, lineString)
|
||||
} else {
|
||||
listItemsStringArray = append(listItemsStringArray, lineString)
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(listItems, endItems...), append(listItemsStringArray, endItemsStringArray...)
|
||||
}
|
||||
|
||||
func renderList(currentRawText string) []template.HTML {
|
||||
listItems, _ := reorderList(currentRawText)
|
||||
for i := range listItems {
|
||||
newHTML := strings.Replace(string(listItems[i]), "</a>", "</a>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "<a href=", "</span><a href=", -1)
|
||||
newHTML = strings.Replace(newHTML, "<li>", "<li>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "</li>", "</span></li>", -1)
|
||||
newHTML = strings.Replace(newHTML, "<li>"+`<span id="`+strconv.Itoa(i)+`" class="deletable"><del>`, "<li><del>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "</del></span></li>", "</span></del></li>", -1)
|
||||
listItems[i] = template.HTML([]byte(newHTML))
|
||||
}
|
||||
return listItems
|
||||
}
|
36
server/migrate.go
Executable file
36
server/migrate.go
Executable file
@ -0,0 +1,36 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"github.com/jcelliott/lumber"
|
||||
)
|
||||
|
||||
func Migrate(pathToOldData, pathToData string, logger *lumber.ConsoleLogger) error {
|
||||
files, err := ioutil.ReadDir(pathToOldData)
|
||||
if len(files) == 0 {
|
||||
return err
|
||||
}
|
||||
s := Site{PathToData: pathToData, Logger: lumber.NewConsoleLogger(lumber.TRACE)}
|
||||
for _, f := range files {
|
||||
if f.Mode().IsDir() {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Migrating %s", f.Name())
|
||||
p := s.Open(f.Name())
|
||||
bData, err := ioutil.ReadFile(path.Join(pathToOldData, f.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.Update(string(bData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = p.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
207
server/page.go
Executable file
207
server/page.go
Executable file
@ -0,0 +1,207 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/versionedtext"
|
||||
)
|
||||
|
||||
// Page is the basic struct
|
||||
type Page struct {
|
||||
Site *Site
|
||||
|
||||
Name string
|
||||
Text versionedtext.VersionedText
|
||||
Meta string
|
||||
RenderedPage string
|
||||
IsLocked bool
|
||||
PassphraseToUnlock string
|
||||
IsEncrypted bool
|
||||
IsPrimedForSelfDestruct bool
|
||||
IsPublished bool
|
||||
UnlockedFor string
|
||||
}
|
||||
|
||||
func (p Page) LastEditTime() time.Time {
|
||||
return time.Unix(p.LastEditUnixTime(), 0)
|
||||
}
|
||||
|
||||
func (p Page) LastEditUnixTime() int64 {
|
||||
return p.Text.LastEditTime() / 1000000000
|
||||
}
|
||||
|
||||
func (s *Site) Open(name string) (p *Page) {
|
||||
p = new(Page)
|
||||
p.Site = s
|
||||
p.Name = name
|
||||
p.Text = versionedtext.NewVersionedText("")
|
||||
p.Render()
|
||||
bJSON, err := ioutil.ReadFile(path.Join(s.PathToData, encodeToBase32(strings.ToLower(name))+".json"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(bJSON, &p)
|
||||
if err != nil {
|
||||
p = new(Page)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type DirectoryEntry struct {
|
||||
Path string
|
||||
Length int
|
||||
Numchanges int
|
||||
LastEdited time.Time
|
||||
}
|
||||
|
||||
func (d DirectoryEntry) LastEditTime() string {
|
||||
return d.LastEdited.Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
}
|
||||
|
||||
func (d DirectoryEntry) Name() string {
|
||||
return d.Path
|
||||
}
|
||||
|
||||
func (d DirectoryEntry) Size() int64 {
|
||||
return int64(d.Length)
|
||||
}
|
||||
|
||||
func (d DirectoryEntry) Mode() os.FileMode {
|
||||
return os.ModePerm
|
||||
}
|
||||
|
||||
func (d DirectoryEntry) ModTime() time.Time {
|
||||
return d.LastEdited
|
||||
}
|
||||
|
||||
func (d DirectoryEntry) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (d DirectoryEntry) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Site) DirectoryList() []os.FileInfo {
|
||||
files, _ := ioutil.ReadDir(s.PathToData)
|
||||
entries := make([]os.FileInfo, len(files))
|
||||
for i, f := range files {
|
||||
name := DecodeFileName(f.Name())
|
||||
p := s.Open(name)
|
||||
entries[i] = DirectoryEntry{
|
||||
Path: name,
|
||||
Length: len(p.Text.GetCurrent()),
|
||||
Numchanges: p.Text.NumEdits(),
|
||||
LastEdited: time.Unix(p.Text.LastEditTime()/1000000000, 0),
|
||||
}
|
||||
}
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].ModTime().After(entries[j].ModTime()) })
|
||||
return entries
|
||||
}
|
||||
|
||||
type UploadEntry struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (s *Site) UploadList() ([]os.FileInfo, error) {
|
||||
paths, err := filepath.Glob(path.Join(s.PathToData, "sha256*"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]os.FileInfo, len(paths))
|
||||
for i := range paths {
|
||||
result[i], err = os.Stat(paths[i])
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func DecodeFileName(s string) string {
|
||||
s2, _ := decodeFromBase32(strings.Split(s, ".")[0])
|
||||
return s2
|
||||
}
|
||||
|
||||
// Update cleans the text and updates the versioned text
|
||||
// and generates a new render
|
||||
func (p *Page) Update(newText string) error {
|
||||
// Trim space from end
|
||||
newText = strings.TrimRight(newText, "\n\t ")
|
||||
|
||||
// Update the versioned text
|
||||
p.Text.Update(newText)
|
||||
|
||||
// Render the new page
|
||||
p.Render()
|
||||
|
||||
return p.Save()
|
||||
}
|
||||
|
||||
var rBracketPage = regexp.MustCompile(`\[\[(.*?)\]\]`)
|
||||
|
||||
func (p *Page) Render() {
|
||||
if p.IsEncrypted {
|
||||
p.RenderedPage = "<code>" + p.Text.GetCurrent() + "</code>"
|
||||
return
|
||||
}
|
||||
|
||||
// Convert [[page]] to [page](/page/view)
|
||||
currentText := p.Text.GetCurrent()
|
||||
for _, s := range rBracketPage.FindAllString(currentText, -1) {
|
||||
currentText = strings.Replace(currentText, s, "["+s[2:len(s)-2]+"](/"+s[2:len(s)-2]+"/view)", 1)
|
||||
}
|
||||
p.Text.Update(currentText)
|
||||
p.RenderedPage = MarkdownToHtml(p.Text.GetCurrent())
|
||||
}
|
||||
|
||||
func (p *Page) Save() error {
|
||||
p.Site.saveMut.Lock()
|
||||
defer p.Site.saveMut.Unlock()
|
||||
bJSON, err := json.MarshalIndent(p, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"), bJSON, 0644)
|
||||
}
|
||||
|
||||
func (p *Page) ChildPageNames() []string {
|
||||
prefix := strings.ToLower(p.Name + ": ")
|
||||
files, err := filepath.Glob(path.Join(p.Site.PathToData, "*"))
|
||||
if err != nil {
|
||||
panic("Filepath pattern cannot be malformed")
|
||||
}
|
||||
|
||||
result := []string{}
|
||||
for i := range files {
|
||||
basename := filepath.Base(files[i])
|
||||
if strings.HasSuffix(basename, ".json") {
|
||||
cname, err := decodeFromBase32(basename[:len(basename)-len(".json")])
|
||||
if err == nil && strings.HasPrefix(strings.ToLower(cname), prefix) {
|
||||
result = append(result, cname)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Page) IsNew() bool {
|
||||
return !exists(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"))
|
||||
}
|
||||
|
||||
func (p *Page) Erase() error {
|
||||
p.Site.Logger.Trace("Erasing " + p.Name)
|
||||
return os.Remove(path.Join(p.Site.PathToData, encodeToBase32(strings.ToLower(p.Name))+".json"))
|
||||
}
|
||||
|
||||
func (p *Page) Published() bool {
|
||||
return p.IsPublished
|
||||
}
|
74
server/page_test.go
Executable file
74
server/page_test.go
Executable file
@ -0,0 +1,74 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListFiles(t *testing.T) {
|
||||
pathToData = "testdata"
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
defer os.RemoveAll(pathToData)
|
||||
s := Site{PathToData: pathToData}
|
||||
p := s.Open("testpage")
|
||||
p.Update("Some data")
|
||||
p = s.Open("testpage2")
|
||||
p.Update("A different bunch of data")
|
||||
p = s.Open("testpage3")
|
||||
p.Update("Not much else")
|
||||
n := s.DirectoryList()
|
||||
if len(n) != 3 {
|
||||
t.Error("Expected three directory entries")
|
||||
t.FailNow()
|
||||
}
|
||||
if n[0].Name() != "testpage" {
|
||||
t.Error("Expected testpage to be first")
|
||||
}
|
||||
if n[1].Name() != "testpage2" {
|
||||
t.Error("Expected testpage2 to be second")
|
||||
}
|
||||
if n[2].Name() != "testpage3" {
|
||||
t.Error("Expected testpage3 to be last")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneral(t *testing.T) {
|
||||
pathToData = "testdata"
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
defer os.RemoveAll(pathToData)
|
||||
s := Site{PathToData: pathToData}
|
||||
p := s.Open("testpage")
|
||||
err := p.Update("**bold**")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if strings.TrimSpace(p.RenderedPage) != "<p><strong>bold</strong></p>" {
|
||||
t.Errorf("Did not render: '%s'", p.RenderedPage)
|
||||
}
|
||||
err = p.Update("**bold** and *italic*")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
p.Save()
|
||||
|
||||
p2 := s.Open("testpage")
|
||||
if strings.TrimSpace(p2.RenderedPage) != "<p><strong>bold</strong> and <em>italic</em></p>" {
|
||||
t.Errorf("Did not render: '%s'", p2.RenderedPage)
|
||||
}
|
||||
|
||||
p3 := s.Open("testpage: childpage")
|
||||
err = p3.Update("**child content**")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
children := p.ChildPageNames()
|
||||
if len(children) != 1 {
|
||||
t.Errorf("Expected 1 child page to be found, got %d", len(children))
|
||||
return
|
||||
}
|
||||
if children[0] != "testpage: childpage" {
|
||||
t.Errorf("Expected child page %s to be found (got %s)", "testpage: childpage", children[0])
|
||||
}
|
||||
}
|
215
server/utils.go
Normal file
215
server/utils.go
Normal file
@ -0,0 +1,215 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
"github.com/shurcooL/github_flavored_markdown"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var animals []string
|
||||
var adjectives []string
|
||||
var aboutPageText string
|
||||
var allowInsecureHtml bool
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
animalsText, _ := Asset("static/text/animals")
|
||||
animals = strings.Split(string(animalsText), ",")
|
||||
adjectivesText, _ := Asset("static/text/adjectives")
|
||||
adjectives = strings.Split(string(adjectivesText), "\n")
|
||||
}
|
||||
|
||||
func randomAnimal() string {
|
||||
return strings.Replace(strings.Title(animals[rand.Intn(len(animals)-1)]), " ", "", -1)
|
||||
}
|
||||
|
||||
func randomAdjective() string {
|
||||
return strings.Replace(strings.Title(adjectives[rand.Intn(len(adjectives)-1)]), " ", "", -1)
|
||||
}
|
||||
|
||||
func randomAlliterateCombo() (combo string) {
|
||||
combo = ""
|
||||
// generate random alliteration thats not been used
|
||||
for {
|
||||
animal := randomAnimal()
|
||||
adjective := randomAdjective()
|
||||
if animal[0] == adjective[0] && len(animal)+len(adjective) < 18 { //&& stringInSlice(strings.ToLower(adjective+animal), takenNames) == false {
|
||||
combo = adjective + animal
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// is there a string in a slice?
|
||||
func stringInSlice(s string, strings []string) bool {
|
||||
for _, k := range strings {
|
||||
if s == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// itob returns an 8-byte big endian representation of v.
|
||||
func itob(v int) []byte {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(v))
|
||||
return b
|
||||
}
|
||||
|
||||
func contentType(filename string) string {
|
||||
switch {
|
||||
case strings.Contains(filename, ".css"):
|
||||
return "text/css"
|
||||
case strings.Contains(filename, ".jpg"):
|
||||
return "image/jpeg"
|
||||
case strings.Contains(filename, ".png"):
|
||||
return "image/png"
|
||||
case strings.Contains(filename, ".js"):
|
||||
return "application/javascript"
|
||||
case strings.Contains(filename, ".xml"):
|
||||
return "application/xml"
|
||||
}
|
||||
return "text/html"
|
||||
}
|
||||
|
||||
func (s *Site) sniffContentType(name string) (string, error) {
|
||||
file, err := os.Open(path.Join(s.PathToData, name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Only the first 512 bytes are used to sniff the content type.
|
||||
buffer := make([]byte, 512)
|
||||
_, err = file.Read(buffer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
|
||||
return http.DetectContentType(buffer), nil
|
||||
}
|
||||
|
||||
var src = rand.NewSource(time.Now().UnixNano())
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
const (
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
)
|
||||
|
||||
// RandStringBytesMaskImprSrc prints a random string
|
||||
func RandStringBytesMaskImprSrc(n int) string {
|
||||
b := make([]byte, n)
|
||||
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = src.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
b[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// HashPassword generates a bcrypt hash of the password using work factor 14.
|
||||
// https://github.com/gtank/cryptopasta/blob/master/hash.go
|
||||
func HashPassword(password string) string {
|
||||
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
return hex.EncodeToString(hash)
|
||||
}
|
||||
|
||||
// CheckPassword securely compares a bcrypt hashed password with its possible
|
||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||
// https://github.com/gtank/cryptopasta/blob/master/hash.go
|
||||
func CheckPasswordHash(password, hashedString string) error {
|
||||
hash, err := hex.DecodeString(hashedString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bcrypt.CompareHashAndPassword(hash, []byte(password))
|
||||
}
|
||||
|
||||
// exists returns whether the given file or directory exists or not
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func MarkdownToHtml(s string) string {
|
||||
unsafe := blackfriday.Run([]byte(s))
|
||||
if allowInsecureHtml {
|
||||
return string(unsafe)
|
||||
}
|
||||
|
||||
pClean := bluemonday.UGCPolicy()
|
||||
pClean.AllowElements("img")
|
||||
pClean.AllowElements("center")
|
||||
pClean.AllowAttrs("alt").OnElements("img")
|
||||
pClean.AllowAttrs("src").OnElements("img")
|
||||
pClean.AllowAttrs("class").OnElements("a")
|
||||
pClean.AllowAttrs("href").OnElements("a")
|
||||
pClean.AllowAttrs("id").OnElements("a")
|
||||
pClean.AllowDataURIImages()
|
||||
html := pClean.SanitizeBytes(unsafe)
|
||||
return string(html)
|
||||
}
|
||||
|
||||
func GithubMarkdownToHTML(s string) string {
|
||||
return string(github_flavored_markdown.Markdown([]byte(s)))
|
||||
}
|
||||
|
||||
func encodeToBase32(s string) string {
|
||||
return encodeBytesToBase32([]byte(s))
|
||||
}
|
||||
|
||||
func encodeBytesToBase32(s []byte) string {
|
||||
return base32.StdEncoding.EncodeToString(s)
|
||||
}
|
||||
|
||||
func decodeFromBase32(s string) (s2 string, err error) {
|
||||
bString, err := base32.StdEncoding.DecodeString(s)
|
||||
s2 = string(bString)
|
||||
return
|
||||
}
|
||||
|
||||
func reverseSliceInt64(s []int64) []int64 {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func reverseSliceString(s []string) []string {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func reverseSliceInt(s []int) []int {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
return s
|
||||
}
|
34
server/utils_test.go
Executable file
34
server/utils_test.go
Executable file
@ -0,0 +1,34 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkAlliterativeAnimal(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
randomAlliterateCombo()
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverseList(t *testing.T) {
|
||||
s := []int64{1, 10, 2, 20}
|
||||
if reverseSliceInt64(s)[0] != 20 {
|
||||
t.Errorf("Could not reverse: %v", s)
|
||||
}
|
||||
s2 := []string{"a", "b", "d", "c"}
|
||||
if reverseSliceString(s2)[0] != "c" {
|
||||
t.Errorf("Could not reverse: %v", s2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashing(t *testing.T) {
|
||||
p := HashPassword("1234")
|
||||
err := CheckPasswordHash("1234", p)
|
||||
if err != nil {
|
||||
t.Errorf("Should be correct password")
|
||||
}
|
||||
err = CheckPasswordHash("1234lkjklj", p)
|
||||
if err == nil {
|
||||
t.Errorf("Should NOT be correct password")
|
||||
}
|
||||
}
|
11
static/css/base-min.css
vendored
Normal file
11
static/css/base-min.css
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/*!
|
||||
Pure v0.6.2
|
||||
Copyright 2013 Yahoo!
|
||||
Licensed under the BSD License.
|
||||
https://github.com/yahoo/pure/blob/master/LICENSE.md
|
||||
*/
|
||||
/*!
|
||||
normalize.css v^3.0 | MIT License | git.io/normalize
|
||||
Copyright (c) Nicolas Gallagher and Jonathan Neal
|
||||
*/
|
||||
/*! 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}
|
596
static/css/bootstrap-theme.css
vendored
596
static/css/bootstrap-theme.css
vendored
@ -1,596 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap v3.3.5 (http://getbootstrap.com)
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Generated using the Bootstrap Customizer (https://getbootstrap.com/customize/?id=1d3a1642fef02a0b2ddb4ede6cb2cb3c)
|
||||
* Config saved to config.json and https://gist.github.com/1d3a1642fef02a0b2ddb4ede6cb2cb3c
|
||||
*/
|
||||
/*!
|
||||
* Bootstrap v3.3.6 (http://getbootstrap.com)
|
||||
* Copyright 2011-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
.btn-default,
|
||||
.btn-primary,
|
||||
.btn-success,
|
||||
.btn-info,
|
||||
.btn-warning,
|
||||
.btn-danger {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-primary:active,
|
||||
.btn-success:active,
|
||||
.btn-info:active,
|
||||
.btn-warning:active,
|
||||
.btn-danger:active,
|
||||
.btn-default.active,
|
||||
.btn-primary.active,
|
||||
.btn-success.active,
|
||||
.btn-info.active,
|
||||
.btn-warning.active,
|
||||
.btn-danger.active {
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
.btn-default.disabled,
|
||||
.btn-primary.disabled,
|
||||
.btn-success.disabled,
|
||||
.btn-info.disabled,
|
||||
.btn-warning.disabled,
|
||||
.btn-danger.disabled,
|
||||
.btn-default[disabled],
|
||||
.btn-primary[disabled],
|
||||
.btn-success[disabled],
|
||||
.btn-info[disabled],
|
||||
.btn-warning[disabled],
|
||||
.btn-danger[disabled],
|
||||
fieldset[disabled] .btn-default,
|
||||
fieldset[disabled] .btn-primary,
|
||||
fieldset[disabled] .btn-success,
|
||||
fieldset[disabled] .btn-info,
|
||||
fieldset[disabled] .btn-warning,
|
||||
fieldset[disabled] .btn-danger {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.btn-default .badge,
|
||||
.btn-primary .badge,
|
||||
.btn-success .badge,
|
||||
.btn-info .badge,
|
||||
.btn-warning .badge,
|
||||
.btn-danger .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.btn:active,
|
||||
.btn.active {
|
||||
background-image: none;
|
||||
}
|
||||
.btn-default {
|
||||
background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
|
||||
background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#e0e0e0));
|
||||
background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dbdbdb;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
border-color: #ccc;
|
||||
}
|
||||
.btn-default:hover,
|
||||
.btn-default:focus {
|
||||
background-color: #e0e0e0;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-default.active {
|
||||
background-color: #e0e0e0;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
.btn-default.disabled,
|
||||
.btn-default[disabled],
|
||||
fieldset[disabled] .btn-default,
|
||||
.btn-default.disabled:hover,
|
||||
.btn-default[disabled]:hover,
|
||||
fieldset[disabled] .btn-default:hover,
|
||||
.btn-default.disabled:focus,
|
||||
.btn-default[disabled]:focus,
|
||||
fieldset[disabled] .btn-default:focus,
|
||||
.btn-default.disabled.focus,
|
||||
.btn-default[disabled].focus,
|
||||
fieldset[disabled] .btn-default.focus,
|
||||
.btn-default.disabled:active,
|
||||
.btn-default[disabled]:active,
|
||||
fieldset[disabled] .btn-default:active,
|
||||
.btn-default.disabled.active,
|
||||
.btn-default[disabled].active,
|
||||
fieldset[disabled] .btn-default.active {
|
||||
background-color: #e0e0e0;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-primary {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary:hover,
|
||||
.btn-primary:focus {
|
||||
background-color: #265a88;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-primary:active,
|
||||
.btn-primary.active {
|
||||
background-color: #265a88;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary.disabled,
|
||||
.btn-primary[disabled],
|
||||
fieldset[disabled] .btn-primary,
|
||||
.btn-primary.disabled:hover,
|
||||
.btn-primary[disabled]:hover,
|
||||
fieldset[disabled] .btn-primary:hover,
|
||||
.btn-primary.disabled:focus,
|
||||
.btn-primary[disabled]:focus,
|
||||
fieldset[disabled] .btn-primary:focus,
|
||||
.btn-primary.disabled.focus,
|
||||
.btn-primary[disabled].focus,
|
||||
fieldset[disabled] .btn-primary.focus,
|
||||
.btn-primary.disabled:active,
|
||||
.btn-primary[disabled]:active,
|
||||
fieldset[disabled] .btn-primary:active,
|
||||
.btn-primary.disabled.active,
|
||||
.btn-primary[disabled].active,
|
||||
fieldset[disabled] .btn-primary.active {
|
||||
background-color: #265a88;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success:hover,
|
||||
.btn-success:focus {
|
||||
background-color: #419641;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-success:active,
|
||||
.btn-success.active {
|
||||
background-color: #419641;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success.disabled,
|
||||
.btn-success[disabled],
|
||||
fieldset[disabled] .btn-success,
|
||||
.btn-success.disabled:hover,
|
||||
.btn-success[disabled]:hover,
|
||||
fieldset[disabled] .btn-success:hover,
|
||||
.btn-success.disabled:focus,
|
||||
.btn-success[disabled]:focus,
|
||||
fieldset[disabled] .btn-success:focus,
|
||||
.btn-success.disabled.focus,
|
||||
.btn-success[disabled].focus,
|
||||
fieldset[disabled] .btn-success.focus,
|
||||
.btn-success.disabled:active,
|
||||
.btn-success[disabled]:active,
|
||||
fieldset[disabled] .btn-success:active,
|
||||
.btn-success.disabled.active,
|
||||
.btn-success[disabled].active,
|
||||
fieldset[disabled] .btn-success.active {
|
||||
background-color: #419641;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info:hover,
|
||||
.btn-info:focus {
|
||||
background-color: #2aabd2;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-info:active,
|
||||
.btn-info.active {
|
||||
background-color: #2aabd2;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info.disabled,
|
||||
.btn-info[disabled],
|
||||
fieldset[disabled] .btn-info,
|
||||
.btn-info.disabled:hover,
|
||||
.btn-info[disabled]:hover,
|
||||
fieldset[disabled] .btn-info:hover,
|
||||
.btn-info.disabled:focus,
|
||||
.btn-info[disabled]:focus,
|
||||
fieldset[disabled] .btn-info:focus,
|
||||
.btn-info.disabled.focus,
|
||||
.btn-info[disabled].focus,
|
||||
fieldset[disabled] .btn-info.focus,
|
||||
.btn-info.disabled:active,
|
||||
.btn-info[disabled]:active,
|
||||
fieldset[disabled] .btn-info:active,
|
||||
.btn-info.disabled.active,
|
||||
.btn-info[disabled].active,
|
||||
fieldset[disabled] .btn-info.active {
|
||||
background-color: #2aabd2;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning:hover,
|
||||
.btn-warning:focus {
|
||||
background-color: #eb9316;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-warning:active,
|
||||
.btn-warning.active {
|
||||
background-color: #eb9316;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning.disabled,
|
||||
.btn-warning[disabled],
|
||||
fieldset[disabled] .btn-warning,
|
||||
.btn-warning.disabled:hover,
|
||||
.btn-warning[disabled]:hover,
|
||||
fieldset[disabled] .btn-warning:hover,
|
||||
.btn-warning.disabled:focus,
|
||||
.btn-warning[disabled]:focus,
|
||||
fieldset[disabled] .btn-warning:focus,
|
||||
.btn-warning.disabled.focus,
|
||||
.btn-warning[disabled].focus,
|
||||
fieldset[disabled] .btn-warning.focus,
|
||||
.btn-warning.disabled:active,
|
||||
.btn-warning[disabled]:active,
|
||||
fieldset[disabled] .btn-warning:active,
|
||||
.btn-warning.disabled.active,
|
||||
.btn-warning[disabled].active,
|
||||
fieldset[disabled] .btn-warning.active {
|
||||
background-color: #eb9316;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger:hover,
|
||||
.btn-danger:focus {
|
||||
background-color: #c12e2a;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-danger:active,
|
||||
.btn-danger.active {
|
||||
background-color: #c12e2a;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger.disabled,
|
||||
.btn-danger[disabled],
|
||||
fieldset[disabled] .btn-danger,
|
||||
.btn-danger.disabled:hover,
|
||||
.btn-danger[disabled]:hover,
|
||||
fieldset[disabled] .btn-danger:hover,
|
||||
.btn-danger.disabled:focus,
|
||||
.btn-danger[disabled]:focus,
|
||||
fieldset[disabled] .btn-danger:focus,
|
||||
.btn-danger.disabled.focus,
|
||||
.btn-danger[disabled].focus,
|
||||
fieldset[disabled] .btn-danger.focus,
|
||||
.btn-danger.disabled:active,
|
||||
.btn-danger[disabled]:active,
|
||||
fieldset[disabled] .btn-danger:active,
|
||||
.btn-danger.disabled.active,
|
||||
.btn-danger[disabled].active,
|
||||
fieldset[disabled] .btn-danger.active {
|
||||
background-color: #c12e2a;
|
||||
background-image: none;
|
||||
}
|
||||
.thumbnail,
|
||||
.img-thumbnail {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.dropdown-menu > li > a:hover,
|
||||
.dropdown-menu > li > a:focus {
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
.dropdown-menu > .active > a,
|
||||
.dropdown-menu > .active > a:hover,
|
||||
.dropdown-menu > .active > a:focus {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-color: #2e6da4;
|
||||
}
|
||||
.navbar-default {
|
||||
background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
|
||||
background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8));
|
||||
background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.navbar-default .navbar-nav > .open > a,
|
||||
.navbar-default .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
|
||||
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.navbar-brand,
|
||||
.navbar-nav > li > a {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.navbar-inverse {
|
||||
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
|
||||
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222222));
|
||||
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.navbar-inverse .navbar-nav > .open > a,
|
||||
.navbar-inverse .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
|
||||
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.navbar-inverse .navbar-brand,
|
||||
.navbar-inverse .navbar-nav > li > a {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.navbar-static-top,
|
||||
.navbar-fixed-top,
|
||||
.navbar-fixed-bottom {
|
||||
border-radius: 0;
|
||||
}
|
||||
@media (max-width: 318px) {
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
|
||||
color: #fff;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.alert-success {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
|
||||
border-color: #b2dba1;
|
||||
}
|
||||
.alert-info {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
|
||||
border-color: #9acfea;
|
||||
}
|
||||
.alert-warning {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
|
||||
border-color: #f5e79e;
|
||||
}
|
||||
.alert-danger {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
|
||||
border-color: #dca7a7;
|
||||
}
|
||||
.progress {
|
||||
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
|
||||
}
|
||||
.progress-bar {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
|
||||
}
|
||||
.progress-bar-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
|
||||
}
|
||||
.progress-bar-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
|
||||
}
|
||||
.progress-bar-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
|
||||
}
|
||||
.progress-bar-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
|
||||
}
|
||||
.progress-bar-striped {
|
||||
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||
}
|
||||
.list-group {
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
.list-group-item.active,
|
||||
.list-group-item.active:hover,
|
||||
.list-group-item.active:focus {
|
||||
text-shadow: 0 -1px 0 #286090;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
|
||||
border-color: #2b669a;
|
||||
}
|
||||
.list-group-item.active .badge,
|
||||
.list-group-item.active:hover .badge,
|
||||
.list-group-item.active:focus .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.panel {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.panel-default > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
}
|
||||
.panel-primary > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
}
|
||||
.panel-success > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
|
||||
}
|
||||
.panel-info > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
|
||||
}
|
||||
.panel-warning > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
|
||||
}
|
||||
.panel-danger > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
|
||||
}
|
||||
.well {
|
||||
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
|
||||
border-color: #dcdcdc;
|
||||
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
14
static/css/bootstrap-theme.min.css
vendored
14
static/css/bootstrap-theme.min.css
vendored
File diff suppressed because one or more lines are too long
6762
static/css/bootstrap.css
vendored
6762
static/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
14
static/css/bootstrap.min.css
vendored
14
static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,338 +0,0 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -30px;
|
||||
/* Hack to make IE7 behave */
|
||||
*zoom:1;
|
||||
*display:inline;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: none;
|
||||
font-variant-ligatures: none;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor { position: absolute; }
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background: #ffa;
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
|
||||
.CodeMirror span { *vertical-align: text-bottom; }
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
128
static/css/default.css
Normal file
128
static/css/default.css
Normal file
@ -0,0 +1,128 @@
|
||||
body.ListPage span {
|
||||
cursor: pointer;
|
||||
}
|
||||
body {
|
||||
background: #fff;
|
||||
}
|
||||
.success {
|
||||
color: #5cb85c;
|
||||
font-weight: bold;
|
||||
}
|
||||
.failure {
|
||||
color: #d9534f;
|
||||
font-weight: bold;
|
||||
}
|
||||
.pure-menu a {
|
||||
color: #777;
|
||||
}
|
||||
.deleting {
|
||||
opacity: 0.5;
|
||||
}
|
||||
#wrap {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
#pad {
|
||||
height:100%;
|
||||
}
|
||||
|
||||
body.EditPage {
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
body#pad textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
border: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
font-size: 1.0em;
|
||||
font-family: 'Open Sans','Segoe UI',Tahoma,Arial,sans-serif;
|
||||
}
|
||||
|
||||
body#pad.HasDotInName textarea {
|
||||
font-family: "Lucida Console", Monaco, monospace;
|
||||
}
|
||||
|
||||
.markdown-body ul, .markdown-body ol {
|
||||
padding-left: 0em;
|
||||
}
|
||||
|
||||
@media (min-width: 5em) {
|
||||
div#menu, div#rendered, .ChildPageNames, body#pad textarea {
|
||||
padding-left: 2%;
|
||||
padding-right: 2%;
|
||||
}
|
||||
.pure-menu .pure-menu-horizontal {
|
||||
max-width: 300px;
|
||||
}
|
||||
.pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
|
||||
padding-left:1.2em;
|
||||
padding-right:em;
|
||||
}
|
||||
.ChildPageNames ul {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
@media (min-width: 50em) {
|
||||
div#menu, div#rendered, .ChildPageNames, body#pad textarea {
|
||||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
}
|
||||
.pure-menu-disabled, .pure-menu-heading, .pure-menu-link {
|
||||
padding: .5em 1em;
|
||||
}
|
||||
.ChildPageNames ul {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
@media (min-width: 70em) {
|
||||
div#menu, div#rendered, .ChildPageNames, body#pad textarea {
|
||||
padding-left: 15%;
|
||||
padding-right: 15%;
|
||||
}
|
||||
.ChildPageNames ul {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 100em) {
|
||||
div#menu, div#rendered, .ChildPageNames, body#pad textarea {
|
||||
padding-left: 20%;
|
||||
padding-right: 20%;
|
||||
}
|
||||
.ChildPageNames ul {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.ChildPageNames ul {
|
||||
margin: 0.3em 0 0 1.6em;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.ChildPageNames li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ChildPageNames a {
|
||||
color: #0645ad;
|
||||
background: none;
|
||||
}
|
388
static/css/dropzone.css
Normal file
388
static/css/dropzone.css
Normal file
@ -0,0 +1,388 @@
|
||||
/*
|
||||
* The MIT License
|
||||
* Copyright (c) 2012 Matias Meno <m@tias.me>
|
||||
*/
|
||||
@-webkit-keyframes passing-through {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(40px);
|
||||
-moz-transform: translateY(40px);
|
||||
-ms-transform: translateY(40px);
|
||||
-o-transform: translateY(40px);
|
||||
transform: translateY(40px); }
|
||||
30%, 70% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0px);
|
||||
-moz-transform: translateY(0px);
|
||||
-ms-transform: translateY(0px);
|
||||
-o-transform: translateY(0px);
|
||||
transform: translateY(0px); }
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-40px);
|
||||
-moz-transform: translateY(-40px);
|
||||
-ms-transform: translateY(-40px);
|
||||
-o-transform: translateY(-40px);
|
||||
transform: translateY(-40px); } }
|
||||
@-moz-keyframes passing-through {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(40px);
|
||||
-moz-transform: translateY(40px);
|
||||
-ms-transform: translateY(40px);
|
||||
-o-transform: translateY(40px);
|
||||
transform: translateY(40px); }
|
||||
30%, 70% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0px);
|
||||
-moz-transform: translateY(0px);
|
||||
-ms-transform: translateY(0px);
|
||||
-o-transform: translateY(0px);
|
||||
transform: translateY(0px); }
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-40px);
|
||||
-moz-transform: translateY(-40px);
|
||||
-ms-transform: translateY(-40px);
|
||||
-o-transform: translateY(-40px);
|
||||
transform: translateY(-40px); } }
|
||||
@keyframes passing-through {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(40px);
|
||||
-moz-transform: translateY(40px);
|
||||
-ms-transform: translateY(40px);
|
||||
-o-transform: translateY(40px);
|
||||
transform: translateY(40px); }
|
||||
30%, 70% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0px);
|
||||
-moz-transform: translateY(0px);
|
||||
-ms-transform: translateY(0px);
|
||||
-o-transform: translateY(0px);
|
||||
transform: translateY(0px); }
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(-40px);
|
||||
-moz-transform: translateY(-40px);
|
||||
-ms-transform: translateY(-40px);
|
||||
-o-transform: translateY(-40px);
|
||||
transform: translateY(-40px); } }
|
||||
@-webkit-keyframes slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(40px);
|
||||
-moz-transform: translateY(40px);
|
||||
-ms-transform: translateY(40px);
|
||||
-o-transform: translateY(40px);
|
||||
transform: translateY(40px); }
|
||||
30% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0px);
|
||||
-moz-transform: translateY(0px);
|
||||
-ms-transform: translateY(0px);
|
||||
-o-transform: translateY(0px);
|
||||
transform: translateY(0px); } }
|
||||
@-moz-keyframes slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(40px);
|
||||
-moz-transform: translateY(40px);
|
||||
-ms-transform: translateY(40px);
|
||||
-o-transform: translateY(40px);
|
||||
transform: translateY(40px); }
|
||||
30% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0px);
|
||||
-moz-transform: translateY(0px);
|
||||
-ms-transform: translateY(0px);
|
||||
-o-transform: translateY(0px);
|
||||
transform: translateY(0px); } }
|
||||
@keyframes slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(40px);
|
||||
-moz-transform: translateY(40px);
|
||||
-ms-transform: translateY(40px);
|
||||
-o-transform: translateY(40px);
|
||||
transform: translateY(40px); }
|
||||
30% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0px);
|
||||
-moz-transform: translateY(0px);
|
||||
-ms-transform: translateY(0px);
|
||||
-o-transform: translateY(0px);
|
||||
transform: translateY(0px); } }
|
||||
@-webkit-keyframes pulse {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
10% {
|
||||
-webkit-transform: scale(1.1);
|
||||
-moz-transform: scale(1.1);
|
||||
-ms-transform: scale(1.1);
|
||||
-o-transform: scale(1.1);
|
||||
transform: scale(1.1); }
|
||||
20% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); } }
|
||||
@-moz-keyframes pulse {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
10% {
|
||||
-webkit-transform: scale(1.1);
|
||||
-moz-transform: scale(1.1);
|
||||
-ms-transform: scale(1.1);
|
||||
-o-transform: scale(1.1);
|
||||
transform: scale(1.1); }
|
||||
20% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); } }
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); }
|
||||
10% {
|
||||
-webkit-transform: scale(1.1);
|
||||
-moz-transform: scale(1.1);
|
||||
-ms-transform: scale(1.1);
|
||||
-o-transform: scale(1.1);
|
||||
transform: scale(1.1); }
|
||||
20% {
|
||||
-webkit-transform: scale(1);
|
||||
-moz-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
-o-transform: scale(1);
|
||||
transform: scale(1); } }
|
||||
.dropzone, .dropzone * {
|
||||
box-sizing: border-box; }
|
||||
|
||||
.dropzone {
|
||||
min-height: 150px;
|
||||
border: 2px solid rgba(0, 0, 0, 0.3);
|
||||
background: white;
|
||||
padding: 20px 20px; }
|
||||
.dropzone.dz-clickable {
|
||||
cursor: pointer; }
|
||||
.dropzone.dz-clickable * {
|
||||
cursor: default; }
|
||||
.dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * {
|
||||
cursor: pointer; }
|
||||
.dropzone.dz-started .dz-message {
|
||||
display: none; }
|
||||
.dropzone.dz-drag-hover {
|
||||
border-style: solid; }
|
||||
.dropzone.dz-drag-hover .dz-message {
|
||||
opacity: 0.5; }
|
||||
.dropzone .dz-message {
|
||||
text-align: center;
|
||||
margin: 2em 0; }
|
||||
.dropzone .dz-preview {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 16px;
|
||||
min-height: 100px; }
|
||||
.dropzone .dz-preview:hover {
|
||||
z-index: 1000; }
|
||||
.dropzone .dz-preview:hover .dz-details {
|
||||
opacity: 1; }
|
||||
.dropzone .dz-preview.dz-file-preview .dz-image {
|
||||
border-radius: 20px;
|
||||
background: #999;
|
||||
background: linear-gradient(to bottom, #eee, #ddd); }
|
||||
.dropzone .dz-preview.dz-file-preview .dz-details {
|
||||
opacity: 1; }
|
||||
.dropzone .dz-preview.dz-image-preview {
|
||||
background: white; }
|
||||
.dropzone .dz-preview.dz-image-preview .dz-details {
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-ms-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear; }
|
||||
.dropzone .dz-preview .dz-remove {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
border: none; }
|
||||
.dropzone .dz-preview .dz-remove:hover {
|
||||
text-decoration: underline; }
|
||||
.dropzone .dz-preview:hover .dz-details {
|
||||
opacity: 1; }
|
||||
.dropzone .dz-preview .dz-details {
|
||||
z-index: 20;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
font-size: 13px;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 2em 1em;
|
||||
text-align: center;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
line-height: 150%; }
|
||||
.dropzone .dz-preview .dz-details .dz-size {
|
||||
margin-bottom: 1em;
|
||||
font-size: 16px; }
|
||||
.dropzone .dz-preview .dz-details .dz-filename {
|
||||
white-space: nowrap; }
|
||||
.dropzone .dz-preview .dz-details .dz-filename:hover span {
|
||||
border: 1px solid rgba(200, 200, 200, 0.8);
|
||||
background-color: rgba(255, 255, 255, 0.8); }
|
||||
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis; }
|
||||
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
|
||||
border: 1px solid transparent; }
|
||||
.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
padding: 0 0.4em;
|
||||
border-radius: 3px; }
|
||||
.dropzone .dz-preview:hover .dz-image img {
|
||||
-webkit-transform: scale(1.05, 1.05);
|
||||
-moz-transform: scale(1.05, 1.05);
|
||||
-ms-transform: scale(1.05, 1.05);
|
||||
-o-transform: scale(1.05, 1.05);
|
||||
transform: scale(1.05, 1.05);
|
||||
-webkit-filter: blur(8px);
|
||||
filter: blur(8px); }
|
||||
.dropzone .dz-preview .dz-image {
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
position: relative;
|
||||
display: block;
|
||||
z-index: 10; }
|
||||
.dropzone .dz-preview .dz-image img {
|
||||
display: block; }
|
||||
.dropzone .dz-preview.dz-success .dz-success-mark {
|
||||
-webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
-moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
-ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
-o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); }
|
||||
.dropzone .dz-preview.dz-error .dz-error-mark {
|
||||
opacity: 1;
|
||||
-webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
-moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
-ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
-o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); }
|
||||
.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
z-index: 500;
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -27px;
|
||||
margin-top: -27px; }
|
||||
.dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg {
|
||||
display: block;
|
||||
width: 54px;
|
||||
height: 54px; }
|
||||
.dropzone .dz-preview.dz-processing .dz-progress {
|
||||
opacity: 1;
|
||||
-webkit-transition: all 0.2s linear;
|
||||
-moz-transition: all 0.2s linear;
|
||||
-ms-transition: all 0.2s linear;
|
||||
-o-transition: all 0.2s linear;
|
||||
transition: all 0.2s linear; }
|
||||
.dropzone .dz-preview.dz-complete .dz-progress {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.4s ease-in;
|
||||
-moz-transition: opacity 0.4s ease-in;
|
||||
-ms-transition: opacity 0.4s ease-in;
|
||||
-o-transition: opacity 0.4s ease-in;
|
||||
transition: opacity 0.4s ease-in; }
|
||||
.dropzone .dz-preview:not(.dz-processing) .dz-progress {
|
||||
-webkit-animation: pulse 6s ease infinite;
|
||||
-moz-animation: pulse 6s ease infinite;
|
||||
-ms-animation: pulse 6s ease infinite;
|
||||
-o-animation: pulse 6s ease infinite;
|
||||
animation: pulse 6s ease infinite; }
|
||||
.dropzone .dz-preview .dz-progress {
|
||||
opacity: 1;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
height: 16px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
width: 80px;
|
||||
margin-left: -40px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
-webkit-transform: scale(1);
|
||||
border-radius: 8px;
|
||||
overflow: hidden; }
|
||||
.dropzone .dz-preview .dz-progress .dz-upload {
|
||||
background: #333;
|
||||
background: linear-gradient(to bottom, #666, #444);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
-webkit-transition: width 300ms ease-in-out;
|
||||
-moz-transition: width 300ms ease-in-out;
|
||||
-ms-transition: width 300ms ease-in-out;
|
||||
-o-transition: width 300ms ease-in-out;
|
||||
transition: width 300ms ease-in-out; }
|
||||
.dropzone .dz-preview.dz-error .dz-error-message {
|
||||
display: block; }
|
||||
.dropzone .dz-preview.dz-error:hover .dz-error-message {
|
||||
opacity: 1;
|
||||
pointer-events: auto; }
|
||||
.dropzone .dz-preview .dz-error-message {
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
display: block;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.3s ease;
|
||||
-moz-transition: opacity 0.3s ease;
|
||||
-ms-transition: opacity 0.3s ease;
|
||||
-o-transition: opacity 0.3s ease;
|
||||
transition: opacity 0.3s ease;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
top: 130px;
|
||||
left: -10px;
|
||||
width: 140px;
|
||||
background: #be2626;
|
||||
background: linear-gradient(to bottom, #be2626, #a92222);
|
||||
padding: 0.5em 1.2em;
|
||||
color: white; }
|
||||
.dropzone .dz-preview .dz-error-message:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 64px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #be2626; }
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user