mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96cc288fa1 | ||
|
|
a4cd5d11dc | ||
|
|
fbf6398148 | ||
|
|
f3ec63e3a3 | ||
|
|
b873121f10 | ||
|
|
38317b8b66 | ||
|
|
4770798c34 | ||
|
|
de86984076 | ||
|
|
b824de9cfb | ||
|
|
c5ff5a660d | ||
|
|
23fc464464 | ||
|
|
fe0db5759a | ||
|
|
e3327d1520 | ||
|
|
325528a2ec | ||
|
|
abed4141f8 | ||
|
|
77e8a20552 | ||
|
|
8bb5a6b42b | ||
|
|
1f0cda0324 | ||
|
|
5e03d0c5b9 | ||
|
|
e5cb53cf20 | ||
|
|
c542615b62 | ||
|
|
fc908fb04d | ||
|
|
32abcbcde7 | ||
|
|
b6e3f2f440 | ||
|
|
4ac3a97d52 | ||
|
|
78389fda75 | ||
|
|
f6ab9c8b5c |
37
README.md
37
README.md
@@ -2,25 +2,52 @@
|
||||
|
||||
# AwwKoala - [Demo](http://awwkoala.com/)
|
||||
## A Websocket Wiki and Kind Of A List Application
|
||||
[]() [](https://goreportcard.com/report/github.com/schollz/AwwKoala) [](https://gitter.im/schollz/AwwKoala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[]() [](https://goreportcard.com/report/github.com/schollz/AwwKoala) [](https://gitter.im/schollz/AwwKoala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
This is a self-contained wiki webserver that makes sharing easy and _fast_. You can make any page you want, and any page is editable by anyone. Pages load instantly for editing, and have special rendering for whether you want to view as a web page or view as list.
|
||||
This is a self-contained wiki 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, 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://AwwKoala.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://AwwKoala.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://AwwKoala.com/AnythingYouWant).
|
||||
**Simplicity**. The philosophy here is to *just type*. To jot a note, simply load the page at [`/`](http://AwwKoala.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://AwwKoala.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://AwwKoala.com/AnythingYouWant). All pages can be rendered into HTML by adding `/view`. For example, the page [`/AnythingYouWant`](http://AwwKoala.com/AnythingYouWant) is rendered at [`/AnythingYouWant/view`](http://AwwKoala.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 `[[AnythingYouWnat]]`.
|
||||
|
||||
**Viewing**. All pages can be rendered into HTML by adding `/view`. For example, the page [`/AnythingYouWant`](http://AwwKoala.com/AnythingYouWant) is rendered at [`/AnythingYouWant/view`](http://AwwKoala.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 `[[AnythingYouWnat]]`. Math is supported with [Katex](https://github.com/Khan/KaTeX) using `$\frac{1}{2}$` for inline equations and `$$\frac{1}{2}$$` for regular equations.
|
||||

|
||||
|
||||
<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://AwwKoala.com/grocery), goto [`/grocery/list`](http://AwwKoala.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.
|
||||
|
||||

|
||||
|
||||
<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).
|
||||
|
||||
**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!). The automatic URL is an alliterative animal description - of which there are over 500,000 possibilities - so the URL is easy to remember and hard to guess.
|
||||

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

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

|
||||
|
||||
<br>
|
||||
|
||||
**Page locking**. You can apply a password to a page to allow further edits from being available. The whole version tree will still be available. _Note_: This is not available for list mode.
|
||||
|
||||
**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
|
||||
|
||||
First [install Go](https://golang.org/doc/install). Then continue.
|
||||
|
||||
23
db.go
23
db.go
@@ -39,12 +39,15 @@ type WikiData struct {
|
||||
CurrentText string
|
||||
Diffs []string
|
||||
Timestamps []string
|
||||
Encrypted bool
|
||||
Locked string
|
||||
}
|
||||
|
||||
func hasPassword(title string) (bool, error) {
|
||||
title = strings.ToLower(title)
|
||||
if !open {
|
||||
return false, fmt.Errorf("db must be opened before loading")
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
hasPassword := false
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
@@ -77,14 +80,18 @@ func hasPassword(title string) (bool, error) {
|
||||
return hasPassword, nil
|
||||
}
|
||||
|
||||
func getCurrentText(title string, version int) (string, []versionsInfo, bool, time.Duration) {
|
||||
func getCurrentText(title string, version int) (string, []versionsInfo, bool, time.Duration, bool, string) {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
title = strings.ToLower(title)
|
||||
var vi []versionsInfo
|
||||
totalTime := time.Now().Sub(time.Now())
|
||||
isCurrent := true
|
||||
currentText := ""
|
||||
encrypted := false
|
||||
locked := ""
|
||||
if !open {
|
||||
return currentText, vi, isCurrent, totalTime
|
||||
return currentText, vi, isCurrent, totalTime, encrypted, locked
|
||||
}
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
@@ -103,6 +110,8 @@ func getCurrentText(title string, version int) (string, []versionsInfo, bool, ti
|
||||
return err
|
||||
}
|
||||
currentText = p.CurrentText
|
||||
encrypted = p.Encrypted
|
||||
locked = p.Locked
|
||||
if version > -1 && version < len(p.Diffs) {
|
||||
// get that version of text instead
|
||||
currentText = rebuildTextsToDiffN(p, version)
|
||||
@@ -115,13 +124,14 @@ func getCurrentText(title string, version int) (string, []versionsInfo, bool, ti
|
||||
if err != nil {
|
||||
fmt.Printf("Could not get WikiData: %s", err)
|
||||
}
|
||||
return currentText, vi, isCurrent, totalTime
|
||||
return currentText, vi, isCurrent, totalTime, encrypted, locked
|
||||
}
|
||||
|
||||
func (p *WikiData) load(title string) error {
|
||||
title = strings.ToLower(title)
|
||||
if !open {
|
||||
return fmt.Errorf("db must be opened before loading!")
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
@@ -154,7 +164,8 @@ func (p *WikiData) load(title string) error {
|
||||
|
||||
func (p *WikiData) save(newText string) error {
|
||||
if !open {
|
||||
return fmt.Errorf("db must be opened before saving")
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte("datas"))
|
||||
|
||||
73
encryption.go
Executable file
73
encryption.go
Executable file
@@ -0,0 +1,73 @@
|
||||
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)
|
||||
//
|
||||
// }
|
||||
18
main.go
18
main.go
@@ -35,7 +35,7 @@ var RuntimeArgs struct {
|
||||
var VersionNum string
|
||||
|
||||
func main() {
|
||||
VersionNum = "0.9"
|
||||
VersionNum = "0.94"
|
||||
// _, executableFile, _, _ := runtime.Caller(0) // get full path of this file
|
||||
cwd, _ := os.Getwd()
|
||||
databaseFile := path.Join(cwd, "data.db")
|
||||
@@ -45,6 +45,7 @@ func main() {
|
||||
flag.StringVar(&RuntimeArgs.ServerCRT, "crt", "", "location of ssl crt")
|
||||
flag.StringVar(&RuntimeArgs.ServerKey, "key", "", "location of ssl key")
|
||||
flag.StringVar(&RuntimeArgs.WikiName, "w", "AwwKoala", "custom name for wiki")
|
||||
dumpDataset := flag.Bool("dump", false, "flag to dump all data to 'dump' directory")
|
||||
flag.CommandLine.Usage = func() {
|
||||
fmt.Println(`AwwKoala (version ` + VersionNum + `): A Websocket Wiki and Kind Of A List Application
|
||||
run this to start the server and then visit localhost at the port you specify
|
||||
@@ -57,15 +58,22 @@ Options:`)
|
||||
flag.CommandLine.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
if *dumpDataset {
|
||||
fmt.Println("Dumping data to 'dump' folder...")
|
||||
dumpEverything()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
RuntimeArgs.ExternalIP = flag.Arg(0)
|
||||
if RuntimeArgs.ExternalIP == "" {
|
||||
RuntimeArgs.ExternalIP = GetLocalIP() + RuntimeArgs.Port
|
||||
}
|
||||
RuntimeArgs.SourcePath = cwd
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
|
||||
// create programdata bucket
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte("programdata"))
|
||||
if err != nil {
|
||||
@@ -76,10 +84,11 @@ Options:`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
Close()
|
||||
|
||||
// Default page
|
||||
aboutFile, _ := ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "templates/aboutpage.md"))
|
||||
p := WikiData{"about", "", []string{}, []string{}}
|
||||
p := WikiData{"help", "", []string{}, []string{}, false, "zzz"}
|
||||
p.save(string(aboutFile))
|
||||
|
||||
// var q WikiData
|
||||
@@ -92,6 +101,7 @@ Options:`)
|
||||
r.HEAD("/", func(c *gin.Context) { c.Status(200) })
|
||||
r.GET("/:title", editNote)
|
||||
r.GET("/:title/*option", everythingElse)
|
||||
r.POST("/:title/*option", encryptionRoute)
|
||||
r.DELETE("/listitem", deleteListItem)
|
||||
r.DELETE("/deletepage", deletePage)
|
||||
if RuntimeArgs.ServerCRT != "" && RuntimeArgs.ServerKey != "" {
|
||||
|
||||
59
makeBinaries.py
Executable file
59
makeBinaries.py
Executable file
@@ -0,0 +1,59 @@
|
||||
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("../")
|
||||
265
routes.go
265
routes.go
@@ -5,6 +5,7 @@ import (
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -17,6 +18,128 @@ import (
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
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")
|
||||
fmt.Println(option, title)
|
||||
var jsonLoad EncryptionPost
|
||||
if option == "/decrypt" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var err error
|
||||
currentText, _, _, _, encrypted, _ := getCurrentText(title, -1)
|
||||
if encrypted == true {
|
||||
currentText, err = decryptString(currentText, jsonLoad.Password)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Inorrect passphrase.",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
} else {
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, false, ""}
|
||||
p.save(currentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Could not bind",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/encrypt" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, true, ""}
|
||||
p.save(encryptString(jsonLoad.Text, jsonLoad.Password))
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/lock" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.Locked = jsonLoad.Password
|
||||
p.save(p.CurrentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/unlock" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(p.Locked) > 0 && p.Locked == jsonLoad.Password {
|
||||
p.Locked = ""
|
||||
p.save(p.CurrentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Unlocked!",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Incorrect password!",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func newNote(c *gin.Context) {
|
||||
title := randomAlliterateCombo()
|
||||
c.Redirect(302, "/"+title)
|
||||
@@ -28,8 +151,8 @@ func editNote(c *gin.Context) {
|
||||
wshandler(c.Writer, c.Request)
|
||||
} else if title == "robots.txt" {
|
||||
c.Data(200, "text/plain", []byte(robotsTxt))
|
||||
} else if strings.ToLower(title) == "about" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||
c.Redirect(302, "/about/view")
|
||||
} else if strings.ToLower(title) == "help" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||
c.Redirect(302, "/Help/view")
|
||||
} else {
|
||||
locked, _ := hasPassword(title)
|
||||
if locked {
|
||||
@@ -37,32 +160,29 @@ func editNote(c *gin.Context) {
|
||||
} else {
|
||||
version := c.DefaultQuery("version", "-1")
|
||||
versionNum, _ := strconv.Atoi(version)
|
||||
currentText, versions, currentVersion, totalTime := getCurrentText(title, versionNum)
|
||||
numRows := len(strings.Split(currentText, "\n")) + 10
|
||||
if currentVersion {
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"ExternalIP": RuntimeArgs.ExternalIP,
|
||||
"CurrentText": currentText,
|
||||
"NumRows": numRows,
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTime,
|
||||
"SocketType": RuntimeArgs.Socket,
|
||||
})
|
||||
} else {
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"ExternalIP": RuntimeArgs.ExternalIP,
|
||||
"CurrentText": currentText,
|
||||
"NumRows": numRows,
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTime,
|
||||
"SocketType": RuntimeArgs.Socket,
|
||||
"NoEdit": true,
|
||||
})
|
||||
currentText, versions, currentVersion, totalTime, encrypted, locked := getCurrentText(title, versionNum)
|
||||
if encrypted || len(locked) > 0 {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
numRows := len(strings.Split(currentText, "\n")) + 10
|
||||
totalTimeString := totalTime.String()
|
||||
if totalTime.Seconds() < 1 {
|
||||
totalTimeString = "< 1 s"
|
||||
}
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"ExternalIP": RuntimeArgs.ExternalIP,
|
||||
"CurrentText": currentText,
|
||||
"NumRows": numRows,
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTimeString,
|
||||
"SocketType": RuntimeArgs.Socket,
|
||||
"NoEdit": !currentVersion,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -73,14 +193,21 @@ func everythingElse(c *gin.Context) {
|
||||
title := c.Param("title")
|
||||
if option == "/view" {
|
||||
version := c.DefaultQuery("version", "-1")
|
||||
noprompt := c.DefaultQuery("noprompt", "-1")
|
||||
versionNum, _ := strconv.Atoi(version)
|
||||
if strings.ToLower(title) == "about" {
|
||||
if strings.ToLower(title) == "help" {
|
||||
versionNum = -1
|
||||
}
|
||||
currentText, versions, _, totalTime := getCurrentText(title, versionNum)
|
||||
renderMarkdown(c, currentText, title, versions, "", totalTime)
|
||||
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)
|
||||
} else if title == "ls" && option == "/"+RuntimeArgs.AdminKey && len(RuntimeArgs.AdminKey) > 1 {
|
||||
renderMarkdown(c, listEverything(), "ls", nil, RuntimeArgs.AdminKey, time.Now().Sub(time.Now()))
|
||||
renderMarkdown(c, listEverything(), "ls", nil, RuntimeArgs.AdminKey, time.Now().Sub(time.Now()), false, false, false)
|
||||
} else if option == "/list" {
|
||||
renderList(c, title)
|
||||
} else if title == "static" {
|
||||
@@ -99,7 +226,7 @@ func serveStaticFile(c *gin.Context, option string) {
|
||||
}
|
||||
}
|
||||
|
||||
func renderMarkdown(c *gin.Context, currentText string, title string, versions []versionsInfo, AdminKey string, totalTime time.Duration) {
|
||||
func renderMarkdown(c *gin.Context, currentText string, title string, versions []versionsInfo, AdminKey string, totalTime time.Duration, encrypted bool, noprompt bool, locked bool) {
|
||||
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)
|
||||
@@ -128,25 +255,23 @@ func renderMarkdown(c *gin.Context, currentText string, title string, versions [
|
||||
html2 = strings.Replace(html2, "&#91;", "[", -1)
|
||||
html2 = strings.Replace(html2, "&#93;", "]", -1)
|
||||
html2 = strings.Replace(html2, "&35;", "#", -1)
|
||||
|
||||
if AdminKey == "" {
|
||||
c.HTML(http.StatusOK, "view.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"Body": template.HTML([]byte(html2)),
|
||||
"TotalTime": totalTime.String(),
|
||||
"Versions": versions,
|
||||
})
|
||||
} else {
|
||||
c.HTML(http.StatusOK, "view.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"Body": template.HTML([]byte(html2)),
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTime.String(),
|
||||
"AdminKey": AdminKey,
|
||||
})
|
||||
totalTimeString := totalTime.String()
|
||||
if totalTime.Seconds() < 1 {
|
||||
totalTimeString = "< 1 s"
|
||||
}
|
||||
c.HTML(http.StatusOK, "view.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"Body": template.HTML([]byte(html2)),
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTimeString,
|
||||
"AdminKey": AdminKey,
|
||||
"Encrypted": encrypted,
|
||||
"Locked": locked,
|
||||
"Prompt": noprompt,
|
||||
"LockedOrEncrypted": locked || encrypted,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func reorderList(text string) ([]template.HTML, []string) {
|
||||
@@ -191,8 +316,8 @@ func reorderList(text string) ([]template.HTML, []string) {
|
||||
}
|
||||
|
||||
func renderList(c *gin.Context, title string) {
|
||||
if strings.ToLower(title) == "about" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||
c.Redirect(302, "/about/view")
|
||||
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))
|
||||
@@ -200,7 +325,14 @@ func renderList(c *gin.Context, title string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(p.CurrentText)
|
||||
currentText := p.CurrentText
|
||||
if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
if p.Encrypted || len(p.Locked) > 0 {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
|
||||
pClean := bluemonday.UGCPolicy()
|
||||
pClean.AllowElements("img")
|
||||
pClean.AllowAttrs("alt").OnElements("img")
|
||||
@@ -262,8 +394,8 @@ func deletePage(c *gin.Context) {
|
||||
fmt.Println(adminKey)
|
||||
fmt.Println(deleteName)
|
||||
// if adminKey == RuntimeArgs.AdminKey || true == true {
|
||||
if strings.ToLower(deleteName) != "about" {
|
||||
p := WikiData{strings.ToLower(deleteName), "", []string{}, []string{}}
|
||||
if strings.ToLower(deleteName) != "help" {
|
||||
p := WikiData{strings.ToLower(deleteName), "", []string{}, []string{}, false, ""}
|
||||
p.save("")
|
||||
}
|
||||
// // remove from program data
|
||||
@@ -307,6 +439,8 @@ func deletePage(c *gin.Context) {
|
||||
}
|
||||
|
||||
func listEverything() string {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
everything := `| Title | Current size | Changes | Total Size | |
|
||||
| --------- |-------------| -----| ------------- | ------------- |
|
||||
`
|
||||
@@ -328,3 +462,24 @@ func listEverything() string {
|
||||
})
|
||||
return everything
|
||||
}
|
||||
|
||||
func dumpEverything() {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
err := os.MkdirAll("dump", 0777)
|
||||
if err != nil {
|
||||
fmt.Println("Already exists")
|
||||
}
|
||||
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))
|
||||
fmt.Println(string(k), len(p.CurrentText))
|
||||
ioutil.WriteFile(path.Join("dump", string(k)+".md"), []byte(p.CurrentText), 0644)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
.yue {
|
||||
font: 400 18px/1.62 "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", sans-serif;
|
||||
color: #444443;
|
||||
color: #333334;
|
||||
}
|
||||
|
||||
.windows .yue {
|
||||
@@ -31,7 +31,9 @@ background-color: rgba(0,0,0,0.2);
|
||||
.yue h5,
|
||||
.yue h6 {
|
||||
font-family: "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", "SimSun", sans-serif;
|
||||
color: #222223;
|
||||
color: #333333;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.yue h1 {
|
||||
@@ -89,6 +91,7 @@ text-decoration-color: rgba(0, 0, 0, 0.4);
|
||||
color: #555;
|
||||
-moz-text-decoration-color: rgba(0, 0, 0, 0.6);
|
||||
text-decoration-color: rgba(0, 0, 0, 0.6);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.yue h1 a,
|
||||
@@ -255,7 +258,7 @@ box-sizing: border-box;
|
||||
.yue td {
|
||||
text-align: left;
|
||||
padding: 4px 8px 4px 10px;
|
||||
border: 1px solid #dadada;
|
||||
border: 1px solid #c1c1bd;
|
||||
}
|
||||
|
||||
.yue td {
|
||||
@@ -263,7 +266,7 @@ vertical-align: top;
|
||||
}
|
||||
|
||||
.yue tr:nth-child(even) {
|
||||
background-color: #efefee;
|
||||
background-color: #dadad8;
|
||||
}
|
||||
|
||||
.yue iframe {
|
||||
@@ -298,16 +301,16 @@ background: none;
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0.4em 1em 6em;
|
||||
background: #fff;
|
||||
background: #fdfdfd;
|
||||
}
|
||||
.yue {
|
||||
max-width: 800px;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container{
|
||||
max-width: 800px;
|
||||
max-width: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
1
static/img/Main1.gif.REMOVED.git-id
Normal file
1
static/img/Main1.gif.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
1892f890ee1c6ae802ecaab5a49dcd8d030efd0e
|
||||
1
static/img/Main2.gif.REMOVED.git-id
Normal file
1
static/img/Main2.gif.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
d80db96bd467d88ad89eef066efbaf80c3266a76
|
||||
1
static/img/Main3.gif.REMOVED.git-id
Normal file
1
static/img/Main3.gif.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
391fc67f2278d89464f4fbd4d2e54af582abb5c0
|
||||
1
static/img/Main4.gif.REMOVED.git-id
Normal file
1
static/img/Main4.gif.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
9199fc12a7b46b9f704513b9520fe0f2939fc481
|
||||
1
static/img/Main5.gif.REMOVED.git-id
Normal file
1
static/img/Main5.gif.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
7242bf5cfc0ed67be7bb3177e6ba0b8eabdfd6ec
|
||||
1
static/img/Main6.gif.REMOVED.git-id
Normal file
1
static/img/Main6.gif.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
60b3cdbea2c28d78a9f853d75aa99bd98d814df7
|
||||
1
static/img/Main7.gif.REMOVED.git-id
Normal file
1
static/img/Main7.gif.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
2a331627a6afd0f05e00ea4eb56f58904a8168a7
|
||||
@@ -1,3 +1,4 @@
|
||||
var selfDestruct = false;
|
||||
$(document).ready(function() {
|
||||
var isTyping = false;
|
||||
var typingTimer; //timer identifier
|
||||
@@ -30,6 +31,17 @@ $(document).ready(function() {
|
||||
console.log("Done typing")
|
||||
updateInterval = setInterval(updateText, pollToGetNewestCopyInterval);
|
||||
document.title = "[SAVED] " + title_name;
|
||||
if ($('#emit_data').val().indexOf("self-destruct\n") > -1 || $('#emit_data').val().indexOf("\nself-destruct") > -1) {
|
||||
if (selfDestruct == false) {
|
||||
selfDestruct = true;
|
||||
swal({ title: "Info", text: "This page is primed to self-destruct.", timer: 1000, showConfirmButton: true });
|
||||
}
|
||||
} else {
|
||||
if (selfDestruct == true) {
|
||||
selfDestruct = false;
|
||||
swal({ title: "Info", text: "This page is no longer primed to self-destruct.", timer: 1000, showConfirmButton: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uhoh() {
|
||||
|
||||
@@ -2,21 +2,50 @@
|
||||
|
||||
# AwwKoala
|
||||
## A Websocket Wiki and Kind Of A List Application
|
||||

|
||||

|
||||
|
||||
This is a self-contained wiki webserver that makes sharing easy and _fast_. You can make any page you want, and any page is editable by anyone. Pages load instantly for editing, and have special rendering for whether you want to view as a web page or view as list. **AwwKoala** is also [Open Source](https://github.com/schollz/AwwKoala).
|
||||
This is a self-contained wiki 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, and listifying. Read on to learn more about the features. **AwwKoala** is also [Open Source](https://github.com/schollz/AwwKoala).
|
||||
|
||||
## Features
|
||||
**Simplicity**. The philosophy here is to *just type*. To jot a note, simply load the page at [`/`](/) 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 [`/`](/) 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`](/AnythingYouWant).
|
||||
**Simplicity**. The philosophy here is to *just type*. To jot a note, simply load the page at [`/`](http://AwwKoala.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://AwwKoala.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://AwwKoala.com/AnythingYouWant). All pages can be rendered into HTML by adding `/view`. For example, the page [`/AnythingYouWant`](http://AwwKoala.com/AnythingYouWant) is rendered at [`/AnythingYouWant/view`](http://AwwKoala.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 `[[AnythingYouWnat]]`.
|
||||
|
||||
**Viewing**. All pages can be rendered into HTML by adding `/view`. For example, the page [`/AnythingYouWant`](/AnythingYouWant) is rendered at [`/AnythingYouWant/view`](/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]]`. Math is supported with [Katex](https://github.com/Khan/KaTeX) using `$\frac{1}{2}$` for inline equations and `$$\frac{1}{2}$$` for regular equations.
|
||||

|
||||
|
||||
**Listifying**. If you are writing a list and you want to tick off things really easily, just add `/list`. For example, after editing [`/grocery`](/grocery), goto [`/grocery/list`](/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.
|
||||
<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!). The automatic URL is an alliterative animal description - of which there are over 500,000 possibilities - so the URL is easy to remember and hard to guess.
|
||||
**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://AwwKoala.com/grocery), goto [`/grocery/list`](http://AwwKoala.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.
|
||||
|
||||

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

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

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

|
||||
|
||||
<br>
|
||||
|
||||
**Page locking**. You can apply a password to a page to allow further edits from being available. The whole version tree will still be available. _Note_: This is not available for list mode.
|
||||
|
||||
**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.
|
||||
|
||||
|
||||
# Contact
|
||||
Any other comments, questions or anything at all, just <a href="https://twitter.com/intent/tweet?screen_name=zack_118" class="twitter-mention-button" data-related="zack_118">tweet me @zack_118</a>
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
|
||||
|
||||
<script src="/static/js/jquery.autogrowtextarea.min.js"></script>
|
||||
{{if .NoEdit}} {{else}} <script src="/static/js/websockets.js"></script> {{end}}
|
||||
{{if .NoEdit}} {{else}}
|
||||
<script src="/static/js/websockets.js"></script> {{end}}
|
||||
|
||||
<script>
|
||||
external_ip = '{{ .ExternalIP }}'
|
||||
@@ -41,13 +42,11 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container{
|
||||
.container {
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<script src="/static/js/sweetalert-dev.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/sweetalert.css">
|
||||
@@ -74,7 +73,7 @@
|
||||
<div id="navbar" class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown active">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" class="active"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" class="active"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">Time edited: {{ .TotalTime }}</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
@@ -85,13 +84,15 @@
|
||||
{{ end }}
|
||||
<li><a href="/{{ .Title }}">Current</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li class="dropdown-header">Options</li>
|
||||
<li><a href="#" id="{{ .Title }}" class="deleteable">Erase</a></li>
|
||||
<li class="dropdown-header">Options</li>
|
||||
<li><a href="#" class="postencrypt">Encrypt</a></li>
|
||||
<li><a href="#" class="postlock">Lock</a></li>
|
||||
<li><a href="#" id="{{ .Title }}" class="deleteable">Erase</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/{{ .Title }}/view"><span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span> View</a></li>
|
||||
<li><a href="/{{ .Title }}/list"><span class="glyphicon glyphicon-align-left" aria-hidden="true"></span> List</a></li>
|
||||
<li><a href="/about/view"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> About</a></li>
|
||||
<li><a href="/Help/view"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--/.nav-collapse -->
|
||||
@@ -111,33 +112,158 @@
|
||||
$("#emit_data").autoGrow();
|
||||
});
|
||||
|
||||
$(document).keydown(function(e){
|
||||
if( e.which === 90 && e.ctrlKey && e.shiftKey ){
|
||||
console.log('control + shift + z');
|
||||
window.location = "/{{ .Title }}/view";
|
||||
$(document).keydown(function(e) {
|
||||
if (e.which === 90 && e.ctrlKey && e.shiftKey) {
|
||||
console.log('control + shift + z');
|
||||
window.location = "/{{ .Title }}/view";
|
||||
}
|
||||
});
|
||||
|
||||
$(document).keydown(function(e){
|
||||
if( e.which === 76 && e.ctrlKey && e.shiftKey ){
|
||||
console.log('control + shift + l');
|
||||
window.location = "/{{ .Title }}/list";
|
||||
}
|
||||
$(document).keydown(function(e) {
|
||||
if (e.which === 76 && e.ctrlKey && e.shiftKey) {
|
||||
console.log('control + shift + l');
|
||||
window.location = "/{{ .Title }}/list";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('.postencrypt').click(function(event) {
|
||||
var pass1 = "";
|
||||
var pass2 = "";
|
||||
event.preventDefault();
|
||||
swal({
|
||||
title: "Encryption",
|
||||
text: "Enter your passphrase:",
|
||||
type: "input",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
animation: "slide-from-top",
|
||||
inputPlaceholder: "Write something"
|
||||
}, function(inputValue) {
|
||||
if (inputValue === false) return false;
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("You need to write something!");
|
||||
return false
|
||||
}
|
||||
pass1 = inputValue;
|
||||
swal({
|
||||
title: "Encryption",
|
||||
text: "Enter your passphrase again:",
|
||||
type: "input",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
animation: "slide-from-top",
|
||||
inputPlaceholder: "Write something"
|
||||
}, function(inputValue) {
|
||||
if (inputValue === false) return false;
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("You need to write something!");
|
||||
return false
|
||||
}
|
||||
pass2 = inputValue
|
||||
if (pass1 == pass2) {
|
||||
swal("Encryption", "Passwords match!", "success");
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
//the url where you want to sent the userName and password to
|
||||
url: '/{{ .Title }}/encrypt',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({
|
||||
text: $('#emit_data').val(),
|
||||
password: pass1
|
||||
}),
|
||||
success: function (data) {
|
||||
if (data['success'] == true) {
|
||||
swal("Encryption", "Encrypted!", "success");
|
||||
window.location.href = '/{{ .Title }}/view?noprompt=1';
|
||||
} else {
|
||||
swal("Encryption", "Something went wrong.", "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
swal("Encryption", "Passwords do not match.", "error");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$('.postlock').click(function(event) {
|
||||
var pass1 = "";
|
||||
var pass2 = "";
|
||||
event.preventDefault();
|
||||
swal({
|
||||
title: "Locking",
|
||||
text: "Enter your passphrase:",
|
||||
type: "input",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
animation: "slide-from-top",
|
||||
inputPlaceholder: "Write something"
|
||||
}, function(inputValue) {
|
||||
if (inputValue === false) return false;
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("You need to write something!");
|
||||
return false
|
||||
}
|
||||
pass1 = inputValue;
|
||||
swal({
|
||||
title: "Locking",
|
||||
text: "Enter your passphrase again:",
|
||||
type: "input",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
animation: "slide-from-top",
|
||||
inputPlaceholder: "Write something"
|
||||
}, function(inputValue) {
|
||||
if (inputValue === false) return false;
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("You need to write something!");
|
||||
return false
|
||||
}
|
||||
pass2 = inputValue
|
||||
if (pass1 == pass2) {
|
||||
swal("Locking", "Passwords match!", "success");
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
//the url where you want to sent the userName and password to
|
||||
url: '/{{ .Title }}/lock',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({
|
||||
text: $('#emit_data').val(),
|
||||
password: pass1
|
||||
}),
|
||||
success: function (data) {
|
||||
if (data['success'] == true) {
|
||||
swal("Locking", "Page locked!", "success");
|
||||
window.location.href = '/{{ .Title }}/view';
|
||||
} else {
|
||||
swal("Locking", "Something went wrong.", "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
swal("Encryption", "Passwords do not match.", "error");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('.deleteable').click(function(event) {
|
||||
event.preventDefault();
|
||||
var deleteName = $(this).attr('id')
|
||||
var href = $(this).attr('href')
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: "You will not be able to recover /{{ .Title }}!",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
closeOnConfirm: false
|
||||
title: "Are you sure?",
|
||||
text: "You will not be able to recover /{{ .Title }}!",
|
||||
type: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
closeOnConfirm: false
|
||||
}, function() {
|
||||
$.ajax({
|
||||
url: "/deletepage" + '?' + $.param({
|
||||
@@ -147,7 +273,9 @@
|
||||
type: 'DELETE',
|
||||
success: function() {
|
||||
swal("Deleted!", "/{{ .Title }} has been deleted.", "success");
|
||||
setTimeout(function(){ window.location.reload(true); }, 1000);
|
||||
setTimeout(function() {
|
||||
window.location.reload(true);
|
||||
}, 1000);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<li><a href="/{{ .Title }}"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit</a></li>
|
||||
<li><a href="/{{ .Title }}/view"><span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span> View</a></li>
|
||||
<li class="active"><a href="/{{ .Title }}/list"><span class="glyphicon glyphicon-align-left" aria-hidden="true"></span> List</a></li>
|
||||
<li><a href="/about/view"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> About</a></li>
|
||||
<li><a href="/Help/view"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--/.nav-collapse -->
|
||||
|
||||
@@ -15,6 +15,8 @@ a.deleteable {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<script src="/static/js/sweetalert-dev.js"></script>
|
||||
<link rel="stylesheet" href="/static/css/sweetalert.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -44,10 +46,19 @@ a.deleteable {
|
||||
<li><a href="/{{ $.Title }}/view?version={{ .VersionNum }}">{{ .VersionDate }}</a></li>
|
||||
{{ end }}
|
||||
<li><a href="/{{ .Title }}/view">Current</a></li>
|
||||
{{ if .LockedOrEncrypted }}
|
||||
<li class="dropdown-header">Options</li>
|
||||
{{ if .Encrypted }}
|
||||
<li><a href="#" class="postdecrypt">Decrypt</a></li>
|
||||
{{end }}
|
||||
{{ if .Locked }}
|
||||
<li><a href="#" class="postunlock">Unlock</a></li>
|
||||
{{end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/{{ .Title }}/list"><span class="glyphicon glyphicon-align-left" aria-hidden="true"></span> List</a></li>
|
||||
<li><a href="/about/view"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> About</a></li>
|
||||
<li><a href="/Help/view"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--/.nav-collapse -->
|
||||
@@ -55,12 +66,140 @@ a.deleteable {
|
||||
</nav>
|
||||
|
||||
<div class="yue">
|
||||
{{ if .Encrypted }}
|
||||
<pre>
|
||||
{{ .Body }}
|
||||
</pre>
|
||||
{{ else }}
|
||||
{{ .Body }}
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
|
||||
{{ if .Encrypted }}
|
||||
{{ if .Prompt }}
|
||||
var pass1 = "";
|
||||
swal({
|
||||
title: "Decryption",
|
||||
text: "Enter your passphrase:",
|
||||
type: "input",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
animation: "slide-from-top",
|
||||
inputPlaceholder: "Write something"
|
||||
}, function(inputValue) {
|
||||
if (inputValue === false) return false;
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("You need to write something!");
|
||||
return false
|
||||
}
|
||||
pass1 = inputValue;
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
//the url where you want to sent the userName and password to
|
||||
url: '/{{ .Title }}/decrypt',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({
|
||||
text: " ",
|
||||
password: pass1
|
||||
}),
|
||||
success: function (data) {
|
||||
if (data['success'] == true) {
|
||||
swal("Decryption", "Decrypted!", "success");
|
||||
window.location.href = '/{{ .Title }}/view';
|
||||
} else {
|
||||
swal("Decryption", data['status'], "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
{{ end }}
|
||||
$('.postdecrypt').click(function(event) {
|
||||
var pass1 = "";
|
||||
event.preventDefault();
|
||||
swal({
|
||||
title: "Decryption",
|
||||
text: "Enter your passphrase:",
|
||||
type: "input",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
animation: "slide-from-top",
|
||||
inputPlaceholder: "Write something"
|
||||
}, function(inputValue) {
|
||||
if (inputValue === false) return false;
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("You need to write something!");
|
||||
return false
|
||||
}
|
||||
pass1 = inputValue;
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
//the url where you want to sent the userName and password to
|
||||
url: '/{{ .Title }}/decrypt',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({
|
||||
text: " ",
|
||||
password: pass1
|
||||
}),
|
||||
success: function (data) {
|
||||
if (data['success'] == true) {
|
||||
swal("Decryption", "Decrypted!", "success");
|
||||
window.location.href = '/{{ .Title }}/view';
|
||||
} else {
|
||||
swal("Decryption", data['status'], "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
{{ end }}
|
||||
|
||||
{{ if .Locked }}
|
||||
|
||||
$('.postunlock').click(function(event) {
|
||||
var pass1 = "";
|
||||
event.preventDefault();
|
||||
swal({
|
||||
title: "Unlock",
|
||||
text: "Enter your passphrase:",
|
||||
type: "input",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
animation: "slide-from-top",
|
||||
inputPlaceholder: "Write something"
|
||||
}, function(inputValue) {
|
||||
if (inputValue === false) return false;
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("You need to write something!");
|
||||
return false
|
||||
}
|
||||
pass1 = inputValue;
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
//the url where you want to sent the userName and password to
|
||||
url: '/{{ .Title }}/unlock',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({
|
||||
text: " ",
|
||||
password: pass1
|
||||
}),
|
||||
success: function (data) {
|
||||
if (data['success'] == true) {
|
||||
swal("Unlock", "Unlocked!", "success");
|
||||
window.location.href = '/{{ .Title }}';
|
||||
} else {
|
||||
swal("Unlock", data['status'], "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
{{ end }}
|
||||
|
||||
|
||||
|
||||
var texi = document.getElementsByClassName("texi");
|
||||
Array.prototype.forEach.call(texi, function(el) {
|
||||
katex.render(el.getAttribute("data-expr"), el, { displayMode: false });
|
||||
@@ -71,6 +210,8 @@ $( document ).ready(function() {
|
||||
katex.render(el.getAttribute("data-expr"), el, { displayMode: true });
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
$(document).keydown(function(e){
|
||||
@@ -88,6 +229,7 @@ $(document).keydown(function(e){
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
{{ if .AdminKey }}
|
||||
$('.deleteable').click(function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user