mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
Start again from scratch
Former-commit-id: 02983a8b61d9fd5ff30de4967c9741ad2478a023 [formerly d55b11ab31697ca40d2d0d80f7261df3c76fc812] [formerly 5d236ead7feece2b73db56d195bdcbcc7623b081 [formerly 4433a9a3b172c82a283df36a601e6c5e581c8ff6 [formerly52abf02847
]]] Former-commit-id: 1176ee8d4cbb8d093ba3b08046c7e93f6c0d835b [formerly f7a072e5000efd8e1b58cfb04336ae8687519d3a] Former-commit-id: 8441f7eff180268e16a037abd2de7e07c1104926 Former-commit-id:2640e02b72
This commit is contained in:
parent
accc3b5f62
commit
dbeea86d6a
32
Dockerfile
32
Dockerfile
@ -1,32 +0,0 @@
|
|||||||
# sudo docker build -t cowyo .
|
|
||||||
# sudo docker run -it -p 8003:8003 -v `pwd`/data:/data cowyo bash
|
|
||||||
FROM ubuntu:16.04
|
|
||||||
|
|
||||||
# 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"]
|
|
156
Godeps/Godeps.json
generated
156
Godeps/Godeps.json
generated
@ -1,156 +0,0 @@
|
|||||||
{
|
|
||||||
"ImportPath": "github.com/schollz/cowyo",
|
|
||||||
"GoVersion": "go1.7",
|
|
||||||
"GodepVersion": "v79",
|
|
||||||
"Deps": [
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/boj/redistore",
|
|
||||||
"Comment": "v1.2",
|
|
||||||
"Rev": "fc113767cd6b051980f260d6dbe84b2740c46ab0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/boltdb/bolt",
|
|
||||||
"Comment": "v1.3.0-58-ge9cf4fa",
|
|
||||||
"Rev": "e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/garyburd/redigo/internal",
|
|
||||||
"Comment": "v1.0.0-17-g908534c",
|
|
||||||
"Rev": "908534c8b97586a4597e3fa195875d2d26502b97"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/garyburd/redigo/redis",
|
|
||||||
"Comment": "v1.0.0-17-g908534c",
|
|
||||||
"Rev": "908534c8b97586a4597e3fa195875d2d26502b97"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gin-gonic/contrib/sessions",
|
|
||||||
"Rev": "4d2dccc9a4541014fec054e483cc76609b97fb16"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gin-gonic/gin",
|
|
||||||
"Comment": "v1.1-65-g049da60",
|
|
||||||
"Rev": "049da60f5114a479f04a668f3d8a6f3b44fe6658"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gin-gonic/gin/binding",
|
|
||||||
"Comment": "v1.1-65-g049da60",
|
|
||||||
"Rev": "049da60f5114a479f04a668f3d8a6f3b44fe6658"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gin-gonic/gin/render",
|
|
||||||
"Comment": "v1.1-65-g049da60",
|
|
||||||
"Rev": "049da60f5114a479f04a668f3d8a6f3b44fe6658"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/golang/protobuf/proto",
|
|
||||||
"Rev": "8ee79997227bf9b34611aee7946ae64735e6fd93"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gorilla/context",
|
|
||||||
"Comment": "v1.1-7-g08b5f42",
|
|
||||||
"Rev": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gorilla/securecookie",
|
|
||||||
"Comment": "v1.1-5-gfa5329f",
|
|
||||||
"Rev": "fa5329f913702981df43dcb2a380bac429c810b5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gorilla/sessions",
|
|
||||||
"Comment": "v1.1-2-g83c8db3",
|
|
||||||
"Rev": "83c8db3bdc9be789e57e3756ffbcffd2d7d40176"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/gorilla/websocket",
|
|
||||||
"Comment": "v1.1.0-6-gadf16b3",
|
|
||||||
"Rev": "adf16b31781325cbd41085c5be901d95b4d1f33d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/jcelliott/lumber",
|
|
||||||
"Rev": "dd349441af25132d146d7095c6693a15431fc9b1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/mattn/go-isatty",
|
|
||||||
"Rev": "30a891c33c7cde7b02a981314b4228ec99380cca"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/microcosm-cc/bluemonday",
|
|
||||||
"Rev": "e79763773ab6222ca1d5a7cbd9d62d83c1f77081"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/russross/blackfriday",
|
|
||||||
"Comment": "v1.4-40-g5f33e7b",
|
|
||||||
"Rev": "5f33e7b7878355cd2b7e6b8eefc48a5472c69f70"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/sergi/go-diff/diffmatchpatch",
|
|
||||||
"Rev": "24e2351369ec4949b2ed0dc5c477afdd4c4034e8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/shurcooL/sanitized_anchor_name",
|
|
||||||
"Rev": "1dba4b3954bc059efc3991ec364f9f9a35f597d2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/bcrypt",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/blowfish",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/cast5",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/openpgp",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/openpgp/armor",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/openpgp/elgamal",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/openpgp/errors",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/openpgp/packet",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/openpgp/s2k",
|
|
||||||
"Rev": "2e74c773682f59dc50a56475f7918dd8fa6dcaf8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/html",
|
|
||||||
"Rev": "007e530097ad7f954752df63046b4036f98ba6a6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/html/atom",
|
|
||||||
"Rev": "007e530097ad7f954752df63046b4036f98ba6a6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/sys/unix",
|
|
||||||
"Rev": "d75a52659825e75fff6158388dddc6a5b04f9ba5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/gin-contrib/sse.v0",
|
|
||||||
"Rev": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/go-playground/validator.v8",
|
|
||||||
"Comment": "v8.18.1",
|
|
||||||
"Rev": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "gopkg.in/yaml.v2",
|
|
||||||
"Rev": "4c78c975fe7c825c6d1466c42be594d1d6f3aba6"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
5
Godeps/Readme
generated
5
Godeps/Readme
generated
@ -1,5 +0,0 @@
|
|||||||
This directory tree is generated automatically by godep.
|
|
||||||
|
|
||||||
Please do not edit.
|
|
||||||
|
|
||||||
See https://github.com/tools/godep for more information.
|
|
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2016 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
|
|
||||||
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:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
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.
|
|
47
Makefile
47
Makefile
@ -1,47 +0,0 @@
|
|||||||
SOURCEDIR=.
|
|
||||||
SOURCES := $(shell find $(SOURCEDIR) -name '*.go')
|
|
||||||
|
|
||||||
BINARY=cowyo
|
|
||||||
|
|
||||||
VERSION=1.1.2
|
|
||||||
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/
|
|
134
README.md
134
README.md
@ -1,134 +0,0 @@
|
|||||||
![Logo](/static/img/cowyo.png)
|
|
||||||
|
|
||||||
# [cowyo.com](http://cowyo.com/)
|
|
||||||
|
|
||||||
[![Version 1.1.1](https://img.shields.io/badge/version-1.1.1-brightgreen.svg)]() [![Go Report Card](https://goreportcard.com/badge/github.com/schollz/cowyo)](https://goreportcard.com/report/github.com/schollz/cowyo) [![Join the chat at https://gitter.im/schollz/cowyo](https://badges.gitter.im/schollz/cowyo.svg)](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]]`.
|
|
||||||
|
|
||||||
![Simply type to edit.](https://raw.githubusercontent.com/schollz/cowyo/master/static/img/help1.gif)
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
**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.
|
|
||||||
|
|
||||||
![Lists are easy to make.](https://raw.githubusercontent.com/schollz/cowyo/master/static/img/help2.gif)
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
**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.
|
|
||||||
|
|
||||||
![Locking is easy.](https://raw.githubusercontent.com/schollz/cowyo/master/static/img/help3.gif)
|
|
||||||
|
|
||||||
<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).
|
|
||||||
|
|
||||||
![Versioning is easy.](https://raw.githubusercontent.com/schollz/cowyo/master/static/img/help4.gif)
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
**Self-destructing messages**. You can write a message [that will delete itself](https://github.com/schollz/cowyo/blob/master/routes.go#L550-L553) 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`".
|
|
||||||
|
|
||||||
![Mission impossible style self-destruction.](https://raw.githubusercontent.com/schollz/cowyo/master/static/img/help5.gif)
|
|
||||||
|
|
||||||
<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_).
|
|
||||||
|
|
||||||
![Security and encryption baked in.](https://raw.githubusercontent.com/schollz/cowyo/master/static/img/help6.gif)
|
|
||||||
|
|
||||||
<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.
|
|
||||||
|
|
||||||
![Coding syntax is provided if you use an extension](https://raw.githubusercontent.com/schollz/cowyo/master/static/img/help7.gif)
|
|
||||||
|
|
||||||
<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
|
|
||||||
|
|
||||||
## From release
|
|
||||||
|
|
||||||
Just [download the latest release](https://github.com/schollz/cowyo/releases/tag/1.1.0), unzip and run. Then open your browser to the specified address.
|
|
||||||
|
|
||||||
## From source
|
|
||||||
|
|
||||||
First [install Go 1.6+](https://golang.org/doc/install).
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go get github.com/schollz/cowyo
|
|
||||||
$ cd $GOPATH/src/github.com/schollz/cowyo
|
|
||||||
$ go build
|
|
||||||
$ ./cowyo
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open your browser to the specified address.
|
|
||||||
|
|
||||||
|
|
||||||
## From Docker
|
|
||||||
|
|
||||||
```
|
|
||||||
$ docker pull schollz/cowyo
|
|
||||||
$ docker run -it -p 8003:8003 -v /local/dir/to/store/data:/data schollz/cowyo
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open your browser to 127.0.0.1:8003.
|
|
||||||
|
|
||||||
|
|
||||||
# Contact
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Acknowledgements
|
|
||||||
|
|
||||||
Thanks to [tscholl2](https://github.com/tscholl2) and [sjsafranek](https://github.com/sjsafranek).
|
|
||||||
|
|
||||||
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").
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2016 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 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:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
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
|
|
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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
//
|
|
||||||
// }
|
|
@ -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,86 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
func get(key string) interface{} {
|
|
||||||
c, _, err := websocket.DefaultDialer.Dial("wss://cowyo.com/ws", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("dial:", err)
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
done := make(chan struct{})
|
|
||||||
var value interface{}
|
|
||||||
go func() {
|
|
||||||
defer c.Close()
|
|
||||||
defer close(done)
|
|
||||||
for {
|
|
||||||
_, message, err := c.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
// log.Println("read:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var m struct {
|
|
||||||
TextData string
|
|
||||||
}
|
|
||||||
json.Unmarshal([]byte(message), &m)
|
|
||||||
json.Unmarshal([]byte(m.TextData), &value)
|
|
||||||
done <- struct{}{}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// ask for something
|
|
||||||
c.WriteMessage(websocket.TextMessage, []byte(`{"Title":"`+key+`", "UpdateClient":true}`))
|
|
||||||
<-done
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func set(key string, message interface{}) interface{} {
|
|
||||||
c, _, err := websocket.DefaultDialer.Dial("wss://cowyo.com/ws", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("dial:", err)
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
done := make(chan struct{})
|
|
||||||
var value interface{}
|
|
||||||
go func() {
|
|
||||||
defer c.Close()
|
|
||||||
defer close(done)
|
|
||||||
for {
|
|
||||||
_, message, err := c.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
// log.Println("read:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var m struct {
|
|
||||||
TextData string
|
|
||||||
}
|
|
||||||
json.Unmarshal([]byte(message), &m)
|
|
||||||
json.Unmarshal([]byte(m.TextData), &value)
|
|
||||||
done <- struct{}{}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// ask for something
|
|
||||||
bJson, _ := json.Marshal(message)
|
|
||||||
aa, _ := json.Marshal(map[string]interface{}{
|
|
||||||
"Title": key,
|
|
||||||
"UpdateServer": true,
|
|
||||||
"TextData": string(bJson),
|
|
||||||
})
|
|
||||||
c.WriteMessage(websocket.TextMessage, aa)
|
|
||||||
<-done
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println(get("data"))
|
|
||||||
fmt.Println(get("data"))
|
|
||||||
m := make(map[string]int)
|
|
||||||
m["some string"] = 29
|
|
||||||
fmt.Println(set("data2", m))
|
|
||||||
fmt.Println(get("data2"))
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
function save(key, value) {
|
|
||||||
c = new WebSocket('wss://cowyo.com/ws');
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
try {
|
|
||||||
c.onopen = function (_) {
|
|
||||||
c.send(JSON.stringify({
|
|
||||||
TextData: JSON.stringify(value),
|
|
||||||
Title: `${key}`,
|
|
||||||
UpdateServer: true,
|
|
||||||
UpdateClient: false,
|
|
||||||
}));
|
|
||||||
return resolve(true);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
return reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// save('hello2', 'world');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function get(key) {
|
|
||||||
c = new WebSocket('wss://cowyo.com/ws');
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
try {
|
|
||||||
c.onmessage = function(evt) {
|
|
||||||
return resolve(JSON.parse(JSON.parse(evt.data).TextData));
|
|
||||||
}
|
|
||||||
c.onopen = function (_) {
|
|
||||||
c.send(JSON.stringify({
|
|
||||||
Title: `${key}`,
|
|
||||||
UpdateClient: true,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
} catch(e) {
|
|
||||||
return reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
181
main.go
181
main.go
@ -1,181 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
"github.com/gin-gonic/contrib/sessions"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
@ -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("../")
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
642
routes.go
642
routes.go
@ -1,642 +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)
|
|
||||||
}
|
|
||||||
hashedPassword, _ := HashPassword([]byte(jsonLoad.Password))
|
|
||||||
p.Locked = string(hashedPassword)
|
|
||||||
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 ||
|
|
||||||
CheckPasswordHash([]byte(p.Locked), []byte(jsonLoad.Password)) == nil) {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert [[page]] to [page](/page/view)
|
|
||||||
r, _ := regexp.Compile("\\[\\[(.*?)\\]\\]")
|
|
||||||
for _, s := range r.FindAllString(p.CurrentText, -1) {
|
|
||||||
p.CurrentText = strings.Replace(p.CurrentText, s, "["+s[2:len(s)-2]+"](/"+s[2:len(s)-2]+"/view)", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
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; }
|
|
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.
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