mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
Major additions. Only updates if has changes
Former-commit-id: 3e0a208c0264f303cefaaa1ff38dcabc2de368a7 [formerly 404c1e940d33da42a5b8178fe769227e3b2c7f70] [formerly 179ebb2430889ecf0d2d6f48c647ff3391030983 [formerly a6d89e5585
]]
Former-commit-id: d8e2f5594b62ab4f1f0a64688d808258bf08ebf4 [formerly a850ea2f7ececc9ac74b1eebcf06e3a4a520828a]
Former-commit-id: 607f80005f5126ea4012e99bd15fdd312525607c
This commit is contained in:
parent
76ea57885f
commit
d1c4a0b1f3
105
db.go
Executable file
105
db.go
Executable file
@ -0,0 +1,105 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *bolt.DB
|
||||||
|
var open bool
|
||||||
|
|
||||||
|
// Open to create the database and open
|
||||||
|
func Open() error {
|
||||||
|
var err error
|
||||||
|
_, filename, _, _ := runtime.Caller(0) // get full path of this file
|
||||||
|
dbfile := path.Join(path.Dir(filename), "data.db")
|
||||||
|
config := &bolt.Options{Timeout: 30 * time.Second}
|
||||||
|
db, err = bolt.Open(dbfile, 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data for storing in DB
|
||||||
|
type CowyoData struct {
|
||||||
|
Title string
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CowyoData) load() error {
|
||||||
|
if !open {
|
||||||
|
return fmt.Errorf("db must be opened before saving!")
|
||||||
|
}
|
||||||
|
err := db.View(func(tx *bolt.Tx) error {
|
||||||
|
var err error
|
||||||
|
b := tx.Bucket([]byte("datas"))
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
k := []byte(p.Title)
|
||||||
|
val := b.Get(k)
|
||||||
|
if val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = p.decode(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Could not get CowyoData: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CowyoData) save() error {
|
||||||
|
if !open {
|
||||||
|
return fmt.Errorf("db must be opened before saving")
|
||||||
|
}
|
||||||
|
err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
bucket, err := tx.CreateBucketIfNotExists([]byte("datas"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create bucket: %s", err)
|
||||||
|
}
|
||||||
|
enc, err := p.encode()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not encode CowyoData: %s", err)
|
||||||
|
}
|
||||||
|
err = bucket.Put([]byte(p.Title), enc)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CowyoData) encode() ([]byte, error) {
|
||||||
|
enc, err := json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return enc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CowyoData) decode(data []byte) error {
|
||||||
|
err := json.Unmarshal(data, &p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
204
main.go
204
main.go
@ -1,120 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/microcosm-cc/bluemonday"
|
|
||||||
"github.com/russross/blackfriday"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var db *bolt.DB
|
|
||||||
var open bool
|
|
||||||
var ExternalIP string
|
var ExternalIP string
|
||||||
var AllowedIPs string
|
var AllowedIPs string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
AllowedIPs = "192.168.1.13,192.168.1.12"
|
AllowedIPs = "192.168.1.13,192.168.1.12,192.168.1.2"
|
||||||
}
|
|
||||||
|
|
||||||
func Open() error {
|
|
||||||
var err error
|
|
||||||
_, filename, _, _ := runtime.Caller(0) // get full path of this file
|
|
||||||
dbfile := path.Join(path.Dir(filename), "data.db")
|
|
||||||
config := &bolt.Options{Timeout: 30 * time.Second}
|
|
||||||
db, err = bolt.Open(dbfile, 0600, config)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Opening BoltDB timed out")
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
open = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Close() {
|
|
||||||
open = false
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type CowyoData struct {
|
|
||||||
Title string
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database functions
|
|
||||||
|
|
||||||
func (p *CowyoData) load() error {
|
|
||||||
if !open {
|
|
||||||
return fmt.Errorf("db must be opened before saving!")
|
|
||||||
}
|
|
||||||
err := db.View(func(tx *bolt.Tx) error {
|
|
||||||
var err error
|
|
||||||
b := tx.Bucket([]byte("datas"))
|
|
||||||
if b == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
k := []byte(p.Title)
|
|
||||||
val := b.Get(k)
|
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = p.decode(val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Could not get CowyoData: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CowyoData) save() error {
|
|
||||||
if !open {
|
|
||||||
return fmt.Errorf("db must be opened before saving!")
|
|
||||||
}
|
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
|
||||||
bucket, err := tx.CreateBucketIfNotExists([]byte("datas"))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create bucket: %s", err)
|
|
||||||
}
|
|
||||||
enc, err := p.encode()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not encode CowyoData: %s", err)
|
|
||||||
}
|
|
||||||
err = bucket.Put([]byte(p.Title), enc)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CowyoData) encode() ([]byte, error) {
|
|
||||||
enc, err := json.Marshal(p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return enc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CowyoData) decode(data []byte) error {
|
|
||||||
err := json.Unmarshal(data, &p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -126,101 +23,8 @@ func main() {
|
|||||||
defer Close()
|
defer Close()
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
r.LoadHTMLGlob("templates/*")
|
r.LoadHTMLGlob("templates/*")
|
||||||
|
r.GET("/", newNote)
|
||||||
r.GET("/", func(c *gin.Context) {
|
r.GET("/:title", editNote)
|
||||||
title := randomAlliterateCombo()
|
r.GET("/:title/*option", everythingElse)
|
||||||
c.Redirect(302, "/"+title)
|
|
||||||
|
|
||||||
})
|
|
||||||
r.GET("/:title", func(c *gin.Context) {
|
|
||||||
title := c.Param("title")
|
|
||||||
if title == "ws" {
|
|
||||||
wshandler(c.Writer, c.Request)
|
|
||||||
} else if strings.ToLower(title) == "about" && strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
|
||||||
c.Redirect(302, "/about/view")
|
|
||||||
} else {
|
|
||||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
|
||||||
"Title": title,
|
|
||||||
"ExternalIP": ExternalIP,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
r.GET("/:title/*option", func(c *gin.Context) {
|
|
||||||
option := c.Param("option")
|
|
||||||
title := c.Param("title")
|
|
||||||
fmt.Println(title, "["+option+"]")
|
|
||||||
if option == "/view" {
|
|
||||||
p := CowyoData{strings.ToLower(title), ""}
|
|
||||||
err := p.load()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe := blackfriday.MarkdownCommon([]byte(p.Text))
|
|
||||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
|
||||||
c.HTML(http.StatusOK, "view.tmpl", gin.H{
|
|
||||||
"Title": title,
|
|
||||||
"Body": template.HTML(html),
|
|
||||||
})
|
|
||||||
|
|
||||||
} else {
|
|
||||||
c.Redirect(302, "/"+title)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Run(":12312")
|
r.Run(":12312")
|
||||||
}
|
}
|
||||||
|
|
||||||
var wsupgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
}
|
|
||||||
|
|
||||||
func wshandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
conn, err := wsupgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to set websocket upgrade: %+v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
t, msg, err := conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
TextData string
|
|
||||||
Title string
|
|
||||||
UpdateServer bool
|
|
||||||
UpdateClient bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var m Message
|
|
||||||
err = json.Unmarshal(msg, &m)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.UpdateServer {
|
|
||||||
p := CowyoData{strings.ToLower(m.Title), m.TextData}
|
|
||||||
err := p.save()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if m.UpdateClient {
|
|
||||||
p := CowyoData{strings.ToLower(m.Title), ""}
|
|
||||||
err := p.load()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
m.TextData = p.Text
|
|
||||||
}
|
|
||||||
newMsg, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
conn.WriteMessage(t, newMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
89
routes.go
Executable file
89
routes.go
Executable file
@ -0,0 +1,89 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
"github.com/russross/blackfriday"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newNote(c *gin.Context) {
|
||||||
|
title := randomAlliterateCombo()
|
||||||
|
c.Redirect(302, "/"+title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editNote(c *gin.Context) {
|
||||||
|
title := c.Param("title")
|
||||||
|
if title == "ws" {
|
||||||
|
wshandler(c.Writer, c.Request)
|
||||||
|
} else if strings.ToLower(title) == "about" && strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||||
|
c.Redirect(302, "/about/view")
|
||||||
|
} else {
|
||||||
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||||
|
"Title": title,
|
||||||
|
"ExternalIP": ExternalIP,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func everythingElse(c *gin.Context) {
|
||||||
|
option := c.Param("option")
|
||||||
|
title := c.Param("title")
|
||||||
|
fmt.Println(title, "["+option+"]")
|
||||||
|
if option == "/view" {
|
||||||
|
renderMarkdown(c, title)
|
||||||
|
} else if option == "/list" {
|
||||||
|
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("./static" + option)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatus(404)
|
||||||
|
} else {
|
||||||
|
c.Data(200, contentType(option), []byte(staticFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderMarkdown(c *gin.Context, title string) {
|
||||||
|
p := CowyoData{strings.ToLower(title), ""}
|
||||||
|
err := p.load()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
unsafe := blackfriday.MarkdownCommon([]byte(p.Text))
|
||||||
|
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||||
|
c.HTML(http.StatusOK, "view.tmpl", gin.H{
|
||||||
|
"Title": title,
|
||||||
|
"Body": template.HTML(html),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderList(c *gin.Context, title string) {
|
||||||
|
p := CowyoData{strings.ToLower(title), ""}
|
||||||
|
err := p.load()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
listItems := []string{}
|
||||||
|
for _, line := range strings.Split(p.Text, "\n") {
|
||||||
|
if len(line) > 1 {
|
||||||
|
listItems = append(listItems, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(listItems)
|
||||||
|
c.HTML(http.StatusOK, "list.tmpl", gin.H{
|
||||||
|
"Title": title,
|
||||||
|
"ListItems": listItems,
|
||||||
|
})
|
||||||
|
}
|
304
static/css/view.css
Executable file
304
static/css/view.css
Executable file
@ -0,0 +1,304 @@
|
|||||||
|
/**
|
||||||
|
* yue.css
|
||||||
|
*
|
||||||
|
* yue.css is designed for readable content.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013 - 2014 by Hsiaoming Yang.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.yue {
|
||||||
|
font: 400 18px/1.62 "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", sans-serif;
|
||||||
|
color: #444443;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows .yue {
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Georgia", "SimSun", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue ::-moz-selection {
|
||||||
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue ::selection {
|
||||||
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue h1,
|
||||||
|
.yue h2,
|
||||||
|
.yue h3,
|
||||||
|
.yue h4,
|
||||||
|
.yue h5,
|
||||||
|
.yue h6 {
|
||||||
|
font-family: "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", "SimSun", sans-serif;
|
||||||
|
color: #222223;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue h1 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue > h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0.83em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue h3 {
|
||||||
|
font-size: 1.17em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue h4,
|
||||||
|
.yue h5,
|
||||||
|
.yue h6 {
|
||||||
|
font-size: 1em;
|
||||||
|
margin: 1.6em 0 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue h6 {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1.46em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue a {
|
||||||
|
color: #111;
|
||||||
|
word-wrap: break-word;
|
||||||
|
-moz-text-decoration-color: rgba(0, 0, 0, 0.4);
|
||||||
|
text-decoration-color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue a:hover {
|
||||||
|
color: #555;
|
||||||
|
-moz-text-decoration-color: rgba(0, 0, 0, 0.6);
|
||||||
|
text-decoration-color: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue h1 a,
|
||||||
|
.yue h2 a,
|
||||||
|
.yue h3 a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue strong,
|
||||||
|
.yue b {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #222223;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue em,
|
||||||
|
.yue i {
|
||||||
|
font-style: italic;
|
||||||
|
color: #222223;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 0.2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue a img {
|
||||||
|
/* Remove border on IE */
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue figure {
|
||||||
|
position: relative;
|
||||||
|
clear: both;
|
||||||
|
outline: 0;
|
||||||
|
margin: 10px 0 30px;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue figure img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: auto auto 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue figure figcaption {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666665;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue figure figcaption a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #666665;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue hr {
|
||||||
|
display: block;
|
||||||
|
width: 14%;
|
||||||
|
margin: 40px auto 34px;
|
||||||
|
border: 0 none;
|
||||||
|
border-top: 3px solid #dededc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue blockquote {
|
||||||
|
margin: 0 0 1.64em 0;
|
||||||
|
border-left: 3px solid #dadada;
|
||||||
|
padding-left: 12px;
|
||||||
|
color: #666664;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue blockquote a {
|
||||||
|
color: #666664;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue ul,
|
||||||
|
.yue ol {
|
||||||
|
margin: 0 0 24px 6px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue ul {
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue li {
|
||||||
|
margin-bottom: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue li ul,
|
||||||
|
.yue li ol {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue li ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue li ul ul {
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue li p {
|
||||||
|
margin: 0.4em 0 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue .unstyled {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue code,
|
||||||
|
.yue tt {
|
||||||
|
color: #808080;
|
||||||
|
font-size: 0.96em;
|
||||||
|
background-color: #f9f9f7;
|
||||||
|
padding: 1px 2px;
|
||||||
|
border: 1px solid #dadada;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue pre {
|
||||||
|
margin: 1.64em 0;
|
||||||
|
padding: 7px;
|
||||||
|
border: none;
|
||||||
|
border-left: 3px solid #dadada;
|
||||||
|
padding-left: 10px;
|
||||||
|
overflow: auto;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 0.96em;
|
||||||
|
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
|
color: #4c4c4c;
|
||||||
|
background-color: #f9f9f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue pre code,
|
||||||
|
.yue pre tt {
|
||||||
|
color: #4c4c4c;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue table {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
font-size: 0.96em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue th,
|
||||||
|
.yue td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 4px 8px 4px 10px;
|
||||||
|
border: 1px solid #dadada;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue tr:nth-child(even) {
|
||||||
|
background-color: #efefee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue iframe {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue figure iframe {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue table pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1100px) {
|
||||||
|
.yue blockquote {
|
||||||
|
margin-left: -24px;
|
||||||
|
padding-left: 20px;
|
||||||
|
border-width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yue blockquote blockquote {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.4em 1em 6em;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.yue {
|
||||||
|
max-width: 650px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
60
static/js/cowyo.js
Executable file
60
static/js/cowyo.js
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
var isTyping = false;
|
||||||
|
var typingTimer; //timer identifier
|
||||||
|
var updateInterval;
|
||||||
|
var doneTypingInterval = 1000; //time in ms, 5 second for example
|
||||||
|
|
||||||
|
//on keyup, start the countdown
|
||||||
|
$('#emit').keyup(function() {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
clearInterval(updateInterval);
|
||||||
|
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
||||||
|
});
|
||||||
|
|
||||||
|
//on keydown, clear the countdown
|
||||||
|
$('#emit').keydown(function() {
|
||||||
|
clearTimeout(typingTimer);
|
||||||
|
clearInterval(updateInterval);
|
||||||
|
document.title = "[UNSAVED] " + title_name;
|
||||||
|
});
|
||||||
|
|
||||||
|
//user is "finished typing," do something
|
||||||
|
function doneTyping() {
|
||||||
|
payload = JSON.stringify({ TextData: $('#emit_data').val(), Title: title_name, UpdateServer: true, UpdateClient: false })
|
||||||
|
send(payload)
|
||||||
|
console.log("Done typing")
|
||||||
|
updateInterval = setInterval(updateText, doneTypingInterval);
|
||||||
|
document.title = "[SAVED] " + title_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateText() {
|
||||||
|
console.log("Getting server's latest copy")
|
||||||
|
payload = JSON.stringify({ TextData: $('#emit_data').val(), Title: title_name, UpdateServer: false, UpdateClient: true })
|
||||||
|
send(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// websockets
|
||||||
|
url = 'ws://'+external_ip+'/ws';
|
||||||
|
c = new WebSocket(url);
|
||||||
|
|
||||||
|
send = function(data){
|
||||||
|
console.log("Sending: " + data)
|
||||||
|
c.send(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.onmessage = function(msg){
|
||||||
|
console.log(msg)
|
||||||
|
data = JSON.parse(msg.data);
|
||||||
|
if (data.UpdateClient == true) {
|
||||||
|
console.log("Updating...")
|
||||||
|
$('#emit_data').val(data.TextData)
|
||||||
|
document.title = "[LOADED] " + title_name;
|
||||||
|
}
|
||||||
|
console.log(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.onopen = function(){
|
||||||
|
updateText();
|
||||||
|
updateInterval = setInterval(updateText, doneTypingInterval);
|
||||||
|
}
|
||||||
|
});
|
0
templates/base.tmpl
Executable file
0
templates/base.tmpl
Executable file
@ -23,100 +23,16 @@
|
|||||||
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<script src="https://code.jquery.com/jquery-1.8.1.min.js"></script>
|
<script src="/static/js/jquery-1.8.1.min.js"></script>
|
||||||
|
<script src="/static/js/jquery.autogrowtextarea.min.js"></script>
|
||||||
|
<script src="/static/js/cowyo.js"></script>
|
||||||
<script>
|
<script>
|
||||||
jQuery.fn.autoGrow = function() {
|
external_ip = '{{ .ExternalIP }}'
|
||||||
return this.each(function() {
|
title_name = '{{ .Title }}'
|
||||||
var createMirror = function(textarea) {
|
|
||||||
jQuery(textarea).after('<div class="autogrow-textarea-mirror"></div>');
|
|
||||||
return jQuery(textarea).next(".autogrow-textarea-mirror")[0]
|
|
||||||
};
|
|
||||||
var sendContentToMirror = function(textarea) {
|
|
||||||
mirror.innerHTML = String(textarea.value).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ").replace(/\n/g, "<br />") + ".<br/>.";
|
|
||||||
if (jQuery(textarea).height() != jQuery(mirror).height()) jQuery(textarea).height(jQuery(mirror).height())
|
|
||||||
};
|
|
||||||
var growTextarea = function() {
|
|
||||||
sendContentToMirror(this)
|
|
||||||
};
|
|
||||||
var mirror = createMirror(this);
|
|
||||||
mirror.style.display = "none";
|
|
||||||
mirror.style.wordWrap = "break-word";
|
|
||||||
mirror.style.padding = jQuery(this).css("padding");
|
|
||||||
mirror.style.width = jQuery(this).css("width");
|
|
||||||
mirror.style.fontFamily = jQuery(this).css("font-family");
|
|
||||||
mirror.style.fontSize = jQuery(this).css("font-size");
|
|
||||||
mirror.style.lineHeight = jQuery(this).css("line-height");
|
|
||||||
this.style.overflow = "hidden";
|
|
||||||
this.style.minHeight = this.rows + "em";
|
|
||||||
this.onkeyup = growTextarea;
|
|
||||||
sendContentToMirror(this)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<script charset="utf-8" type="text/javascript">
|
|
||||||
$(document).ready(function() {
|
|
||||||
var isTyping = false;
|
|
||||||
var typingTimer; //timer identifier
|
|
||||||
var updateInterval;
|
|
||||||
var doneTypingInterval = 1000; //time in ms, 5 second for example
|
|
||||||
|
|
||||||
//on keyup, start the countdown
|
|
||||||
$('#emit').keyup(function() {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
clearInterval(updateInterval);
|
|
||||||
typingTimer = setTimeout(doneTyping, doneTypingInterval);
|
|
||||||
});
|
|
||||||
|
|
||||||
//on keydown, clear the countdown
|
|
||||||
$('#emit').keydown(function() {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
clearInterval(updateInterval);
|
|
||||||
document.title = "[UNSAVED] {{ .Title }}";
|
|
||||||
});
|
|
||||||
|
|
||||||
//user is "finished typing," do something
|
|
||||||
function doneTyping() {
|
|
||||||
payload = JSON.stringify({ TextData: $('#emit_data').val(), Title: "{{ .Title }}", UpdateServer: true, UpdateClient: false })
|
|
||||||
send(payload)
|
|
||||||
console.log("Done typing")
|
|
||||||
updateInterval = setInterval(updateText, doneTypingInterval);
|
|
||||||
document.title = "[SAVED] {{ .Title }}";
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateText() {
|
|
||||||
console.log("Getting server's latest copy")
|
|
||||||
payload = JSON.stringify({ TextData: $('#emit_data').val(), Title: "{{ .Title }}", UpdateServer: false, UpdateClient: true })
|
|
||||||
send(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// websockets
|
|
||||||
url = 'ws://{{ .ExternalIP }}/ws';
|
|
||||||
c = new WebSocket(url);
|
|
||||||
|
|
||||||
send = function(data){
|
|
||||||
console.log("Sending: " + data)
|
|
||||||
c.send(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.onmessage = function(msg){
|
|
||||||
console.log(msg)
|
|
||||||
data = JSON.parse(msg.data);
|
|
||||||
if (data.UpdateClient == true) {
|
|
||||||
console.log("Updating...")
|
|
||||||
$('#emit_data').val(data.TextData)
|
|
||||||
document.title = "[LOADED] {{ .Title }}";
|
|
||||||
}
|
|
||||||
console.log(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.onopen = function(){
|
|
||||||
updateText();
|
|
||||||
updateInterval = setInterval(updateText, doneTypingInterval);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
44
templates/list.tmpl
Executable file
44
templates/list.tmpl
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>{{ .Title }}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href=/static/img/favicon/apple-icon-57x57.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href=/static/img/favicon/img/favicon/apple-icon-60x60.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href=/static/img/favicon/apple-icon-72x72.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href=/static/img/favicon/apple-icon-76x76.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href=/static/img/favicon/apple-icon-114x114.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href=/static/img/favicon/apple-icon-120x120.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href=/static/img/favicon/apple-icon-144x144.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href=/static/img/favicon/apple-icon-152x152.png>
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href=/static/img/favicon/apple-icon-180x180.png>
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href=/static/img/favicon/android-icon-192x192.png>
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href=/static/img/favicon/favicon-32x32.png>
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href=/static/img/favicon/favicon-96x96.png>
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href=/static/img/favicon/favicon-16x16.png>
|
||||||
|
<link rel="manifest" href=/static/img/favicon/manifest.json>
|
||||||
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
|
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/view.css">
|
||||||
|
<script src="/static/js/jquery-1.8.1.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="yue">
|
||||||
|
<ul>
|
||||||
|
{{ range .ListItems }}
|
||||||
|
<li> {{ . }}</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -1,346 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href=/static/img/favicon/apple-icon-57x57.png>
|
<link rel="apple-touch-icon" sizes="57x57" href=/static/img/favicon/apple-icon-57x57.png>
|
||||||
<link rel="apple-touch-icon" sizes="60x60" href=/static/img/favicon/img/favicon/apple-icon-60x60.png>
|
<link rel="apple-touch-icon" sizes="60x60" href=/static/img/favicon/img/favicon/apple-icon-60x60.png>
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href=/static/img/favicon/apple-icon-72x72.png>
|
<link rel="apple-touch-icon" sizes="72x72" href=/static/img/favicon/apple-icon-72x72.png>
|
||||||
<link rel="apple-touch-icon" sizes="76x76" href=/static/img/favicon/apple-icon-76x76.png>
|
<link rel="apple-touch-icon" sizes="76x76" href=/static/img/favicon/apple-icon-76x76.png>
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href=/static/img/favicon/apple-icon-114x114.png>
|
<link rel="apple-touch-icon" sizes="114x114" href=/static/img/favicon/apple-icon-114x114.png>
|
||||||
<link rel="apple-touch-icon" sizes="120x120" href=/static/img/favicon/apple-icon-120x120.png>
|
<link rel="apple-touch-icon" sizes="120x120" href=/static/img/favicon/apple-icon-120x120.png>
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href=/static/img/favicon/apple-icon-144x144.png>
|
<link rel="apple-touch-icon" sizes="144x144" href=/static/img/favicon/apple-icon-144x144.png>
|
||||||
<link rel="apple-touch-icon" sizes="152x152" href=/static/img/favicon/apple-icon-152x152.png>
|
<link rel="apple-touch-icon" sizes="152x152" href=/static/img/favicon/apple-icon-152x152.png>
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href=/static/img/favicon/apple-icon-180x180.png>
|
<link rel="apple-touch-icon" sizes="180x180" href=/static/img/favicon/apple-icon-180x180.png>
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href=/static/img/favicon/android-icon-192x192.png>
|
<link rel="icon" type="image/png" sizes="192x192" href=/static/img/favicon/android-icon-192x192.png>
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href=/static/img/favicon/favicon-32x32.png>
|
<link rel="icon" type="image/png" sizes="32x32" href=/static/img/favicon/favicon-32x32.png>
|
||||||
<link rel="icon" type="image/png" sizes="96x96" href=/static/img/favicon/favicon-96x96.png>
|
<link rel="icon" type="image/png" sizes="96x96" href=/static/img/favicon/favicon-96x96.png>
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href=/static/img/favicon/favicon-16x16.png>
|
<link rel="icon" type="image/png" sizes="16x16" href=/static/img/favicon/favicon-16x16.png>
|
||||||
<link rel="manifest" href=/static/img/favicon/manifest.json>
|
<link rel="manifest" href=/static/img/favicon/manifest.json>
|
||||||
<meta name="msapplication-TileColor" content="#ffffff">
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/view.css">
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/**
|
</head>
|
||||||
* yue.css
|
|
||||||
*
|
<body>
|
||||||
* yue.css is designed for readable content.
|
<div class="yue">
|
||||||
*
|
{{ .Body }}
|
||||||
* Copyright (c) 2013 - 2014 by Hsiaoming Yang.
|
</div>
|
||||||
*/
|
|
||||||
|
|
||||||
.yue {
|
</body>
|
||||||
font: 400 18px/1.62 "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", sans-serif;
|
|
||||||
color: #444443;
|
</html>
|
||||||
}
|
|
||||||
|
|
||||||
.windows .yue {
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: "Georgia", "SimSun", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue ::-moz-selection {
|
|
||||||
background-color: rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue ::selection {
|
|
||||||
background-color: rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue h1,
|
|
||||||
.yue h2,
|
|
||||||
.yue h3,
|
|
||||||
.yue h4,
|
|
||||||
.yue h5,
|
|
||||||
.yue h6 {
|
|
||||||
font-family: "Georgia", "Xin Gothic", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei", "SimSun", sans-serif;
|
|
||||||
color: #222223;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue h1 {
|
|
||||||
font-size: 1.8em;
|
|
||||||
margin: 0.67em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue > h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue h2 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: 0.83em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue h3 {
|
|
||||||
font-size: 1.17em;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue h4,
|
|
||||||
.yue h5,
|
|
||||||
.yue h6 {
|
|
||||||
font-size: 1em;
|
|
||||||
margin: 1.6em 0 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue h6 {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1.46em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue a {
|
|
||||||
color: #111;
|
|
||||||
word-wrap: break-word;
|
|
||||||
-moz-text-decoration-color: rgba(0, 0, 0, 0.4);
|
|
||||||
text-decoration-color: rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue a:hover {
|
|
||||||
color: #555;
|
|
||||||
-moz-text-decoration-color: rgba(0, 0, 0, 0.6);
|
|
||||||
text-decoration-color: rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue h1 a,
|
|
||||||
.yue h2 a,
|
|
||||||
.yue h3 a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue strong,
|
|
||||||
.yue b {
|
|
||||||
font-weight: 700;
|
|
||||||
color: #222223;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue em,
|
|
||||||
.yue i {
|
|
||||||
font-style: italic;
|
|
||||||
color: #222223;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
margin: 0.2em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue a img {
|
|
||||||
/* Remove border on IE */
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue figure {
|
|
||||||
position: relative;
|
|
||||||
clear: both;
|
|
||||||
outline: 0;
|
|
||||||
margin: 10px 0 30px;
|
|
||||||
padding: 0;
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue figure img {
|
|
||||||
display: block;
|
|
||||||
max-width: 100%;
|
|
||||||
margin: auto auto 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue figure figcaption {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
left: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666665;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue figure figcaption a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #666665;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue hr {
|
|
||||||
display: block;
|
|
||||||
width: 14%;
|
|
||||||
margin: 40px auto 34px;
|
|
||||||
border: 0 none;
|
|
||||||
border-top: 3px solid #dededc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue blockquote {
|
|
||||||
margin: 0 0 1.64em 0;
|
|
||||||
border-left: 3px solid #dadada;
|
|
||||||
padding-left: 12px;
|
|
||||||
color: #666664;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue blockquote a {
|
|
||||||
color: #666664;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue ul,
|
|
||||||
.yue ol {
|
|
||||||
margin: 0 0 24px 6px;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue ul {
|
|
||||||
list-style-type: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue ol {
|
|
||||||
list-style-type: decimal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue li {
|
|
||||||
margin-bottom: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue li ul,
|
|
||||||
.yue li ol {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue li ul {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue li ul ul {
|
|
||||||
list-style-type: circle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue li p {
|
|
||||||
margin: 0.4em 0 0.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue .unstyled {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue code,
|
|
||||||
.yue tt {
|
|
||||||
color: #808080;
|
|
||||||
font-size: 0.96em;
|
|
||||||
background-color: #f9f9f7;
|
|
||||||
padding: 1px 2px;
|
|
||||||
border: 1px solid #dadada;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue pre {
|
|
||||||
margin: 1.64em 0;
|
|
||||||
padding: 7px;
|
|
||||||
border: none;
|
|
||||||
border-left: 3px solid #dadada;
|
|
||||||
padding-left: 10px;
|
|
||||||
overflow: auto;
|
|
||||||
line-height: 1.5;
|
|
||||||
font-size: 0.96em;
|
|
||||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
||||||
color: #4c4c4c;
|
|
||||||
background-color: #f9f9f7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue pre code,
|
|
||||||
.yue pre tt {
|
|
||||||
color: #4c4c4c;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue table {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
font-size: 0.96em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue th,
|
|
||||||
.yue td {
|
|
||||||
text-align: left;
|
|
||||||
padding: 4px 8px 4px 10px;
|
|
||||||
border: 1px solid #dadada;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue tr:nth-child(even) {
|
|
||||||
background-color: #efefee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue iframe {
|
|
||||||
display: block;
|
|
||||||
max-width: 100%;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue figure iframe {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue table pre {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1100px) {
|
|
||||||
.yue blockquote {
|
|
||||||
margin-left: -24px;
|
|
||||||
padding-left: 20px;
|
|
||||||
border-width: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yue blockquote blockquote {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.4em 1em 6em;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.yue {
|
|
||||||
max-width: 650px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="yue">
|
|
||||||
{{ .Body }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
14
utils.go
14
utils.go
@ -35,3 +35,17 @@ func randomAlliterateCombo() (combo string) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contentType(filename string) string {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(filename, ".css"):
|
||||||
|
return "text/css"
|
||||||
|
case strings.Contains(filename, ".jpg"):
|
||||||
|
return "image/jpeg"
|
||||||
|
case strings.Contains(filename, ".png"):
|
||||||
|
return "image/png"
|
||||||
|
case strings.Contains(filename, ".js"):
|
||||||
|
return "application/javascript"
|
||||||
|
}
|
||||||
|
return "text/html"
|
||||||
|
}
|
||||||
|
65
websockets.go
Executable file
65
websockets.go
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var wsupgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
func wshandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := wsupgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to set websocket upgrade: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, msg, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
TextData string
|
||||||
|
Title string
|
||||||
|
UpdateServer bool
|
||||||
|
UpdateClient bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var m Message
|
||||||
|
err = json.Unmarshal(msg, &m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.UpdateServer {
|
||||||
|
p := CowyoData{strings.ToLower(m.Title), m.TextData}
|
||||||
|
err := p.save()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.UpdateClient {
|
||||||
|
p := CowyoData{strings.ToLower(m.Title), ""}
|
||||||
|
err := p.load()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
m.UpdateClient = len(m.TextData) != len(p.Text)
|
||||||
|
m.TextData = p.Text
|
||||||
|
}
|
||||||
|
newMsg, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
conn.WriteMessage(t, newMsg)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user