Blog edit and preview page
This commit is contained in:
parent
5b051391d6
commit
5009065480
|
@ -2,8 +2,10 @@ package core
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/kirsle/blog/core/forms"
|
||||
"github.com/kirsle/blog/core/models/settings"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
@ -32,5 +34,38 @@ func (b *Blog) SettingsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// Get the current settings.
|
||||
settings, _ := settings.Load()
|
||||
v.Data["s"] = settings
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
redisPort, _ := strconv.Atoi(r.FormValue("redis-port"))
|
||||
redisDB, _ := strconv.Atoi(r.FormValue("redis-db"))
|
||||
form := &forms.Settings{
|
||||
Title: r.FormValue("title"),
|
||||
AdminEmail: r.FormValue("admin-email"),
|
||||
RedisEnabled: r.FormValue("redis-enabled") == "true",
|
||||
RedisHost: r.FormValue("redis-host"),
|
||||
RedisPort: redisPort,
|
||||
RedisDB: redisDB,
|
||||
RedisPrefix: r.FormValue("redis-prefix"),
|
||||
}
|
||||
|
||||
// Copy form values into the settings struct for display, in case of
|
||||
// any validation errors.
|
||||
settings.Site.Title = form.Title
|
||||
settings.Site.AdminEmail = form.AdminEmail
|
||||
settings.Redis.Enabled = form.RedisEnabled
|
||||
settings.Redis.Host = form.RedisHost
|
||||
settings.Redis.Port = form.RedisPort
|
||||
settings.Redis.DB = form.RedisDB
|
||||
settings.Redis.Prefix = form.RedisPrefix
|
||||
err := form.Validate()
|
||||
if err != nil {
|
||||
v.Error = err
|
||||
} else {
|
||||
// Save the settings.
|
||||
settings.Save()
|
||||
b.FlashAndReload(w, r, "Settings have been saved!")
|
||||
return
|
||||
}
|
||||
}
|
||||
b.RenderTemplate(w, r, "admin/settings", v)
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ func New(documentRoot, userRoot string) *Blog {
|
|||
r.HandleFunc("/login", blog.LoginHandler)
|
||||
r.HandleFunc("/logout", blog.LogoutHandler)
|
||||
blog.AdminRoutes(r)
|
||||
blog.BlogRoutes(r)
|
||||
|
||||
r.PathPrefix("/").HandlerFunc(blog.PageHandler)
|
||||
r.NotFoundHandler = http.HandlerFunc(blog.PageHandler)
|
||||
|
|
|
@ -26,7 +26,7 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|||
Form: forms.Setup{},
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
if r.Method == http.MethodPost {
|
||||
form := &forms.Login{
|
||||
Username: r.FormValue("username"),
|
||||
Password: r.FormValue("password"),
|
||||
|
@ -42,7 +42,7 @@ func (b *Blog) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|||
vars.Error = errors.New("bad username or password")
|
||||
} else {
|
||||
// Login OK!
|
||||
vars.Flash = "Login OK!"
|
||||
b.Flash(w, r, "Login OK!")
|
||||
b.Login(w, r, user)
|
||||
|
||||
// A next URL given? TODO: actually get to work
|
||||
|
|
58
core/blog.go
Normal file
58
core/blog.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/kirsle/blog/core/models/posts"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
// BlogRoutes attaches the blog routes to the app.
|
||||
func (b *Blog) BlogRoutes(r *mux.Router) {
|
||||
// Login-required routers.
|
||||
loginRouter := mux.NewRouter()
|
||||
loginRouter.HandleFunc("/blog/edit", b.EditBlog)
|
||||
r.PathPrefix("/blog").Handler(
|
||||
negroni.New(
|
||||
negroni.HandlerFunc(b.LoginRequired),
|
||||
negroni.Wrap(loginRouter),
|
||||
),
|
||||
)
|
||||
|
||||
adminRouter := mux.NewRouter().PathPrefix("/admin").Subrouter().StrictSlash(false)
|
||||
r.HandleFunc("/admin", b.AdminHandler) // so as to not be "/admin/"
|
||||
adminRouter.HandleFunc("/settings", b.SettingsHandler)
|
||||
adminRouter.PathPrefix("/").HandlerFunc(b.PageHandler)
|
||||
r.PathPrefix("/admin").Handler(negroni.New(
|
||||
negroni.HandlerFunc(b.LoginRequired),
|
||||
negroni.Wrap(adminRouter),
|
||||
))
|
||||
}
|
||||
|
||||
// EditBlog is the blog writing and editing page.
|
||||
func (b *Blog) EditBlog(w http.ResponseWriter, r *http.Request) {
|
||||
v := NewVars(map[interface{}]interface{}{
|
||||
"preview": "",
|
||||
})
|
||||
post := posts.New()
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
// Parse from form values.
|
||||
post.LoadForm(r)
|
||||
|
||||
// Previewing, or submitting?
|
||||
switch r.FormValue("submit") {
|
||||
case "preview":
|
||||
v.Data["preview"] = template.HTML(b.RenderMarkdown(post.Body))
|
||||
case "submit":
|
||||
if err := post.Validate(); err != nil {
|
||||
v.Error = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.Data["post"] = post
|
||||
b.RenderTemplate(w, r, "blog/edit", v)
|
||||
}
|
31
core/forms/settings.go
Normal file
31
core/forms/settings.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package forms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/mail"
|
||||
)
|
||||
|
||||
// Settings are the user-facing admin settings.
|
||||
type Settings struct {
|
||||
Title string
|
||||
AdminEmail string
|
||||
RedisEnabled bool
|
||||
RedisHost string
|
||||
RedisPort int
|
||||
RedisDB int
|
||||
RedisPrefix string
|
||||
}
|
||||
|
||||
// Validate the form.
|
||||
func (f Settings) Validate() error {
|
||||
if len(f.Title) == 0 {
|
||||
return errors.New("website title is required")
|
||||
}
|
||||
if f.AdminEmail != "" {
|
||||
_, err := mail.ParseAddress(f.AdminEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -15,7 +15,7 @@ func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
Form: forms.Setup{},
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
if r.Method == http.MethodPost {
|
||||
form := forms.Setup{
|
||||
Username: r.FormValue("username"),
|
||||
Password: r.FormValue("password"),
|
||||
|
@ -49,7 +49,7 @@ func (b *Blog) SetupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// All set!
|
||||
b.Login(w, r, user)
|
||||
b.Redirect(w, "/admin")
|
||||
b.FlashAndRedirect(w, r, "/admin", "Admin user created and logged in.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
9
core/markdown.go
Normal file
9
core/markdown.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package core
|
||||
|
||||
import "github.com/shurcooL/github_flavored_markdown"
|
||||
|
||||
// RenderMarkdown renders markdown to HTML.
|
||||
func (b *Blog) RenderMarkdown(input string) string {
|
||||
output := github_flavored_markdown.Markdown([]byte(input))
|
||||
return string(output)
|
||||
}
|
66
core/models/posts/posts.go
Normal file
66
core/models/posts/posts.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package posts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Post holds information for a blog post.
|
||||
type Post struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Fragment string `json:"fragment"`
|
||||
ContentType string `json:"contentType"`
|
||||
Body string `json:"body"`
|
||||
Privacy string `json:"privacy"`
|
||||
Sticky bool `json:"sticky"`
|
||||
EnableComments bool `json:"enableComments"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// New creates a blank post with sensible defaults.
|
||||
func New() *Post {
|
||||
return &Post{
|
||||
ContentType: "markdown",
|
||||
Privacy: "public",
|
||||
EnableComments: true,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadForm populates the post from form values.
|
||||
func (p *Post) LoadForm(r *http.Request) {
|
||||
id, _ := strconv.Atoi(r.FormValue("id"))
|
||||
|
||||
p.ID = id
|
||||
p.Title = r.FormValue("title")
|
||||
p.Fragment = r.FormValue("fragment")
|
||||
p.ContentType = r.FormValue("content-type")
|
||||
p.Body = r.FormValue("body")
|
||||
p.Privacy = r.FormValue("privacy")
|
||||
p.Sticky = r.FormValue("sticky") == "true"
|
||||
p.EnableComments = r.FormValue("enable-comments") == "true"
|
||||
|
||||
// Ingest the tags.
|
||||
tags := strings.Split(r.FormValue("tags"), ",")
|
||||
p.Tags = []string{}
|
||||
for _, tag := range tags {
|
||||
p.Tags = append(p.Tags, strings.TrimSpace(tag))
|
||||
}
|
||||
}
|
||||
|
||||
// Validate makes sure the required fields are all present.
|
||||
func (p *Post) Validate() error {
|
||||
if p.Title == "" {
|
||||
return errors.New("title is required")
|
||||
}
|
||||
if p.ContentType != "markdown" && p.ContentType != "markdown+html" &&
|
||||
p.ContentType != "html" {
|
||||
return errors.New("invalid setting for ContentType")
|
||||
}
|
||||
if p.Privacy != "public" && p.Privacy != "draft" && p.Privacy != "private" {
|
||||
return errors.New("invalid setting for Privacy")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,9 +1,29 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Flash adds a flash message to the user's session.
|
||||
func (b *Blog) Flash(w http.ResponseWriter, r *http.Request, message string, args ...interface{}) {
|
||||
session := b.Session(r)
|
||||
session.AddFlash(fmt.Sprintf(message, args...))
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
// FlashAndRedirect flashes and redirects in one go.
|
||||
func (b *Blog) FlashAndRedirect(w http.ResponseWriter, r *http.Request, location, message string, args ...interface{}) {
|
||||
b.Flash(w, r, message, args...)
|
||||
b.Redirect(w, location)
|
||||
}
|
||||
|
||||
// FlashAndReload flashes and sends a redirect to the same path.
|
||||
func (b *Blog) FlashAndReload(w http.ResponseWriter, r *http.Request, message string, args ...interface{}) {
|
||||
b.Flash(w, r, message, args...)
|
||||
b.Redirect(w, r.URL.Path)
|
||||
}
|
||||
|
||||
// Redirect sends an HTTP redirect response.
|
||||
func (b *Blog) Redirect(w http.ResponseWriter, location string) {
|
||||
log.Error("Redirect: %s", location)
|
||||
|
|
|
@ -23,7 +23,7 @@ type Vars struct {
|
|||
|
||||
// Common template variables.
|
||||
Message string
|
||||
Flash string
|
||||
Flashes []string
|
||||
Error error
|
||||
Data map[interface{}]interface{}
|
||||
Form forms.Form
|
||||
|
@ -44,7 +44,7 @@ func NewVars(data ...map[interface{}]interface{}) *Vars {
|
|||
}
|
||||
|
||||
// LoadDefaults combines template variables with default, globally available vars.
|
||||
func (v *Vars) LoadDefaults(r *http.Request) {
|
||||
func (v *Vars) LoadDefaults(b *Blog, w http.ResponseWriter, r *http.Request) {
|
||||
// Get the site settings.
|
||||
s, err := settings.Load()
|
||||
if err != nil {
|
||||
|
@ -57,6 +57,16 @@ func (v *Vars) LoadDefaults(r *http.Request) {
|
|||
v.Title = s.Site.Title
|
||||
v.Path = r.URL.Path
|
||||
|
||||
// Add any flashed messages from the endpoint controllers.
|
||||
session := b.Session(r)
|
||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
||||
for _, flash := range flashes {
|
||||
_ = flash
|
||||
v.Flashes = append(v.Flashes, flash.(string))
|
||||
}
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
if user, ok := ctx.Value(userKey).(*users.User); ok {
|
||||
if user.ID > 0 {
|
||||
|
@ -68,7 +78,7 @@ func (v *Vars) LoadDefaults(r *http.Request) {
|
|||
|
||||
// TemplateVars is an interface that describes the template variable struct.
|
||||
type TemplateVars interface {
|
||||
LoadDefaults(*http.Request)
|
||||
LoadDefaults(*Blog, http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// RenderTemplate responds with an HTML template.
|
||||
|
@ -87,9 +97,15 @@ func (b *Blog) RenderTemplate(w http.ResponseWriter, r *http.Request, path strin
|
|||
return err
|
||||
}
|
||||
|
||||
// Useful template functions.
|
||||
log.Error("HERE!!!")
|
||||
t := template.New(filepath.Absolute).Funcs(template.FuncMap{
|
||||
"StringsJoin": strings.Join,
|
||||
})
|
||||
|
||||
// Parse the template files. The layout comes first because it's the wrapper
|
||||
// and allows the filepath template to set the page title.
|
||||
t, err := template.ParseFiles(layout.Absolute, filepath.Absolute)
|
||||
t, err = t.ParseFiles(layout.Absolute, filepath.Absolute)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
|
@ -99,7 +115,7 @@ func (b *Blog) RenderTemplate(w http.ResponseWriter, r *http.Request, path strin
|
|||
if vars == nil {
|
||||
vars = &Vars{}
|
||||
}
|
||||
vars.LoadDefaults(r)
|
||||
vars.LoadDefaults(b, w, r)
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; encoding=UTF-8")
|
||||
err = t.ExecuteTemplate(w, "layout", vars)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{ define "title" }}Untitled{{ end }}
|
||||
{{ define "scripts" }}Default Scripts{{ end }}
|
||||
{{ define "scripts" }}{{ end }}
|
||||
|
||||
{{ define "layout" }}
|
||||
<!DOCTYPE html>
|
||||
|
@ -59,9 +59,9 @@
|
|||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if .Flash }}
|
||||
{{ range .Flashes }}
|
||||
<div class="alert alert-success">
|
||||
{{ .Flash }}
|
||||
{{ . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
@ -159,7 +159,7 @@
|
|||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="text/javascript" src="/js/vue.min.js"></script>
|
||||
<script type="text/javascript" src="/js/bootstrap.min.js"></script>
|
||||
{{ template "scripts" or "" }}
|
||||
|
||||
</body>
|
||||
|
|
|
@ -1,45 +1,16 @@
|
|||
{{ define "title" }}Website Settings{{ end }}
|
||||
{{ define "content" }}
|
||||
<form action="/admin/settings" method="POST">
|
||||
<div id="settings-app" class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#site"
|
||||
:class="{ active: currentTab === 'site'}"
|
||||
v-on:click="currentTab = 'site'">
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
<!-- <li class="nav-item">
|
||||
<a class="nav-link" href="#db"
|
||||
:class="{ active: currentTab === 'db'}"
|
||||
v-on:click="currentTab = 'db'">
|
||||
Database
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#security"
|
||||
:class="{ active: currentTab === 'security'}"
|
||||
v-on:click="currentTab = 'security'">
|
||||
Security
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Hello</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
{{ with .Data.s }}
|
||||
<div class="card-body" v-if="currentTab === 'site'">
|
||||
<div class="card-body">
|
||||
<h3>The Basics</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="title" id="title"
|
||||
name="title"
|
||||
value="{{ .Site.Title }}"
|
||||
placeholder="Website Title">
|
||||
</div>
|
||||
|
@ -47,9 +18,9 @@
|
|||
<div class="form-group">
|
||||
<label for="admin-email">Admin Email</label>
|
||||
<small class="text-muted">For getting notifications about comments, etc.</small>
|
||||
<input type="email"
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="admin-email" id="admin-email"
|
||||
name="admin-email"
|
||||
value="{{ .Site.AdminEmail }}"
|
||||
placeholder="name@domain.com">
|
||||
</div>
|
||||
|
@ -72,22 +43,11 @@
|
|||
Enable Redis
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="redis-prefix">Key Prefix</label>
|
||||
<small class="text-muted">(optional)</small>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="redis-prefix" id="redis-prefix"
|
||||
value="{{ .Redis.Prefix }}"
|
||||
placeholder="blog:">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="redis-host">Redis Host</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="redis-host" id="redis-host"
|
||||
name="redis-host"
|
||||
value="{{ .Redis.Host }}"
|
||||
placeholder="localhost">
|
||||
</div>
|
||||
|
@ -95,7 +55,7 @@
|
|||
<label for="redis-port">Port</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="redis-port" id="redis-port"
|
||||
name="redis-port"
|
||||
value="{{ .Redis.Port }}"
|
||||
placeholder="6379">
|
||||
</div>
|
||||
|
@ -104,7 +64,7 @@
|
|||
<small class="text-muted">0-15</small>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="redis-db" id="redis-db"
|
||||
name="redis-db"
|
||||
value="{{ .Redis.DB }}"
|
||||
placeholder="0">
|
||||
</div>
|
||||
|
@ -113,42 +73,18 @@
|
|||
<small class="text-muted">(optional)</small>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="redis-prefix" id="redis-prefix"
|
||||
name="redis-prefix"
|
||||
value="{{ .Redis.Prefix }}"
|
||||
placeholder="blog:">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body" v-if="currentTab === 'db'">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-body" v-if="currentTab === 'security'">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox"
|
||||
class="form-check-input"
|
||||
name="redis-enabled"
|
||||
value="true"
|
||||
{{ if .Redis.Enabled }}checked{{ end }}>
|
||||
Enable Redis
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="redis-prefix">Key Prefix</label>
|
||||
<small class="text-muted">(optional)</small>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="redis-prefix" id="redis-prefix"
|
||||
value="{{ .Redis.Prefix }}"
|
||||
placeholder="blog:">
|
||||
<button type="submit" class="btn btn-primary">Save Settings</button>
|
||||
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ define "scripts" }}
|
||||
<script type="text/javascript" src="/admin/settings.js"></script>
|
||||
{{ end }}
|
||||
|
|
149
root/blog/edit.gohtml
Normal file
149
root/blog/edit.gohtml
Normal file
|
@ -0,0 +1,149 @@
|
|||
{{ define "title" }}Update Blog{{ end }}
|
||||
{{ define "content" }}
|
||||
<form action="/blog/edit" method="POST">
|
||||
{{ if .Data.preview }}
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
Preview
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ .Data.preview }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with .Data.post }}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h3>Update Blog</h3>
|
||||
|
||||
{{ . }}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="title"
|
||||
value="{{ .Title }}"
|
||||
placeholder="Post Title"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="fragment">URL Fragment</label>
|
||||
<small class="text-muted">
|
||||
You can leave this blank if this is a new post. It will pick a
|
||||
default value based on the title.
|
||||
</small>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="fragment"
|
||||
value="{{ .Fragment }}"
|
||||
placeholder="url-fragment-for-blog-entry"
|
||||
autocomplete="false">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="body">Body</label>
|
||||
|
||||
<div class="form-check form-check-inline">
|
||||
<label class="form-check-label">
|
||||
<input type="radio"
|
||||
class="form-check-input"
|
||||
name="content-type"
|
||||
value="markdown"
|
||||
{{ if eq .ContentType "markdown" }}checked{{ end }}
|
||||
> Markdown
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<label class="form-check-label">
|
||||
<input type="radio"
|
||||
class="form-check-input"
|
||||
name="content-type"
|
||||
value="markdown+html"
|
||||
{{ if eq .ContentType "markdown+html" }}checked{{ end }}
|
||||
> Markdown + HTML
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<label class="form-check-label">
|
||||
<input type="radio"
|
||||
class="form-check-input"
|
||||
name="content-type"
|
||||
value="html"
|
||||
{{ if eq .ContentType "html" }}checked{{ end }}
|
||||
> Raw HTML
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<textarea class="form-control"
|
||||
cols="80"
|
||||
rows="12"
|
||||
name="body"
|
||||
placeholder="Post body goes here">{{ .Body }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tags">Tags</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
name="tags"
|
||||
placeholder="Comma, Separated, List"
|
||||
value="{{ StringsJoin .Tags ", " }}"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="privacy">Privacy</label>
|
||||
<select name="privacy" class="form-control">
|
||||
<option value="public"{{ if eq .Privacy "public" }} selected{{ end }}>Public: everybody can see this post</option>
|
||||
<option value="private"{{ if eq .Privacy "private" }} selected{{ end }}>Private: only site admins can see this post</option>
|
||||
<option value="draft"{{ if eq .Privacy "draft" }} selected{{ end }}>Draft: don't show this post on the blog anywhere</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Options</label>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox"
|
||||
class="form-check-label"
|
||||
name="sticky"
|
||||
value="true"
|
||||
{{ if .Sticky }}checked{{ end }}
|
||||
> Make this post sticky (always on top)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox"
|
||||
class="form-check-label"
|
||||
name="enable-comments"
|
||||
value="true"
|
||||
{{ if .EnableComments }}checked{{ end }}
|
||||
> Enable comments on this post
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit"
|
||||
class="btn btn-success"
|
||||
name="submit"
|
||||
value="preview">
|
||||
Preview
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="btn btn-primary"
|
||||
name="submit"
|
||||
value="post">
|
||||
Publish
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</form>
|
||||
{{ end }}
|
|
@ -30,6 +30,12 @@ h6, .h6 {
|
|||
.form-group label {
|
||||
font-weight: bold;
|
||||
}
|
||||
label.form-check-label {
|
||||
font-weight: normal;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Top nav
|
||||
|
|
Loading…
Reference in New Issue
Block a user