Browse Source

Page is done

master
Gisle Aune 7 years ago
parent
commit
f471aed610
  1. 1
      controllers/listcontroller.go
  2. 171
      controllers/pagecontroller.go
  3. 2
      controllers/usercontroller.go
  4. 14
      model/category.go
  5. 25
      model/header.go
  6. 40
      model/page.go
  7. 2
      model/page_test.go
  8. 12
      model/tag.go
  9. 28
      view/funcs.go
  10. 11
      view/renderer.go
  11. 32
      view/templates/index.tmpl
  12. 12
      view/templates/page/create.tmpl
  13. 37
      view/templates/page/delete.tmpl
  14. 50
      view/templates/page/edit.tmpl
  15. 44
      view/templates/page/view.tmpl
  16. 3
      view/templates/user/login.tmpl
  17. 10
      viewmodel/base.go
  18. 2
      viewmodel/pageview.go

1
controllers/listcontroller.go

@ -58,6 +58,7 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
return true return true
} }
vm.ActiveTag = *tag
vm.Headers, err = model.ListHeadersByTag(category.Key, tag) vm.Headers, err = model.ListHeadersByTag(category.Key, tag)
} else { } else {
vm.Headers, err = model.ListHeadersByCategory(category.Key) vm.Headers, err = model.ListHeadersByCategory(category.Key)

171
controllers/pagecontroller.go

@ -1,10 +1,13 @@
package controllers package controllers
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"git.aiterp.net/gisle/wrouter/response"
"git.aiterp.net/AiteRP/aitestory/model" "git.aiterp.net/AiteRP/aitestory/model"
"git.aiterp.net/AiteRP/aitestory/viewmodel" "git.aiterp.net/AiteRP/aitestory/viewmodel"
@ -99,22 +102,188 @@ func pageCreate(path string, w http.ResponseWriter, req *http.Request, user *aut
return true return true
} }
} }
} else {
// Losing input makes the user sad, let's see to it
// that it doesn't happen.
req.ParseForm()
pc.Page.ParseForm(req.Form)
pc.TagInput = req.Form.Get("tags")
} }
view.Render(w, "create", 200, pc)
view.Render(w, "page/create", 200, pc)
return true return true
} }
func pageView(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool { func pageView(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
if req.Method != "GET" || strings.LastIndex(req.URL.Path, "/") > len(path) {
return false
}
page, err := model.FindPage(req.URL.Path[len(path):])
if err != nil {
response.Text(w, 500, err.Error())
return true
} else if page == nil {
return false
}
pv := viewmodel.PageView{}
pv.Page = page
pv.Setup(user)
view.Render(w, "page/view", 200, pv)
return true
}
func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
if (req.Method != "GET" && req.Method != "POST") || strings.LastIndex(req.URL.Path, "/") > len(path) { if (req.Method != "GET" && req.Method != "POST") || strings.LastIndex(req.URL.Path, "/") > len(path) {
return false return false
} }
page, err := model.FindPage(req.URL.Path[len(path):])
if err != nil {
response.Text(w, 500, err.Error())
return true
} else if page == nil {
return false
}
pv := viewmodel.PageView{}
pv.Page = page
pv.Setup(user)
if user == nil || user.FullID() != page.Author {
view.Render(w, "message/error-access", http.StatusForbidden, path)
return true
}
if req.Method == "POST" {
req.ParseForm()
// Catch sneaky shenanigans
if req.Form.Get("aft") != user.Session.ID {
view.Render(w, "message/error-forgery", http.StatusForbidden, path)
return true
}
// Thy will be done
err := page.Delete()
if err != nil {
// It wasn't done D:
view.Render(w, "message/error-internal", http.StatusInternalServerError, err)
return true
}
// It has been done
view.Render(w, "message/page-deleted", 200, pv)
return true
}
view.Render(w, "page/delete", 200, pv)
return true
}
func pageEdit(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
if (req.Method != "GET" && req.Method != "POST") || strings.LastIndex(req.URL.Path, "/") > len(path) {
return false
}
page, err := model.FindPage(req.URL.Path[len(path):])
if err != nil {
response.Text(w, 500, err.Error())
return true
} else if page == nil {
return false
}
pf := viewmodel.PageForm{}
pf.Setup(user)
pf.Operation = "Edit"
pf.Page = *page
if user == nil {
pf.Error = "You are not logged in"
}
if user != nil && user.FullID() != page.Author {
pf.Error = "Permission denied"
}
if req.Method == "POST" {
req.ParseForm()
errs := pf.Page.ParseForm(req.Form)
if len(errs) > 0 {
pf.Error = "Validation failed: " + errs[0].Error()
}
if len(errs) == 0 && pf.Error == "" {
pf.TagInput = req.Form.Get("tags")
pf.Page.Tags = make([]model.Tag, 0, strings.Count(pf.TagInput, "\n"))
tagLines := strings.Split(pf.TagInput, "\n")
for _, line := range tagLines {
var tagType, tagName string
// Skip empty lines, and allow some accidental letters
if len(line) < 2 {
continue
}
// Parse tokens
tokens := strings.SplitN(line, ":", 2)
if len(tokens) == 2 {
tagType = strings.Trim(tokens[0], "  \t\r")
tagName = strings.Trim(tokens[1], "  \t\r")
} else {
tagType = "*" // Permit untyped tags if it exists.
tagName = strings.Trim(tokens[0], "  \t\r")
}
// Grab the tag
tag, err := model.EnsureTag(tagType, tagName)
if err != nil {
pf.Error = "Check your tags: " + err.Error()
break
}
// Take a copy of it
pf.Page.Tags = append(pf.Page.Tags, *tag)
}
if pf.Error == "" {
pf.Page.EditDate = time.Now()
err := pf.Page.Update()
if err != nil {
pf.Error = "Edit failed: " + err.Error()
} else {
http.Redirect(w, req, "/page/"+pf.Page.ID, 302)
return true
}
}
}
} else {
for _, tag := range page.Tags {
line := fmt.Sprintf("%s: %s\n", tag.Type, tag.Name)
if page.PrimaryTag().ID == tag.ID {
pf.TagInput = line + pf.TagInput
} else {
pf.TagInput += line
}
}
}
view.Render(w, "page/edit", 200, pf)
return true return true
} }
func init() { func init() {
PageController.Function("/create", pageCreate) PageController.Function("/create", pageCreate)
PageController.Function("/edit/", pageEdit)
PageController.Function("/delete/", pageDelete)
PageController.Function("/", pageView) PageController.Function("/", pageView)
} }

2
controllers/usercontroller.go

@ -42,7 +42,7 @@ func userLogin(path string, w http.ResponseWriter, req *http.Request, user *auth
} }
ul.Setup(user) ul.Setup(user)
view.Render(w, "login", 200, ul)
view.Render(w, "user/login", 200, ul)
return true return true
} }

14
model/category.go

@ -21,13 +21,13 @@ func (category *PageCategory) URLRoot() string {
// a limited selection of categories. I may move it to a configuration // a limited selection of categories. I may move it to a configuration
// or the database, but for now I think this list is pretty fixed // or the database, but for now I think this list is pretty fixed
var PageCategories = []PageCategory{ var PageCategories = []PageCategory{
{"OoC", "OoC", "ooc", "OoC content is for announcements, scheduling, general information, or anything that is not in-universe"},
{"Info", "Info", "info", "Information gained during and between RP sessions"},
{"News", "News", "news", "News stories that might be pertinent to ongoing plots"},
{"Item", "Items", "item", "Items relevant to plots, that is more than just a document saved on a character's own omni-tool"},
{"Document", "Documents", "document", "Data files, shadow broker dossiers, and other data that is not inside an item"},
{"Background", "Background", "background", "Rumors, suspicious persons, or inter-RP occurences that may be noticed"},
{"Story", "Stories", "story", "Background stories and inter-RP character intearactions"},
{"OoC", "OoC", "O", "OoC content is for announcements, scheduling, general information, or anything that is not in-universe"},
{"Info", "Info", "i", "Information gained during and between RP sessions"},
{"News", "News", "N", "News stories that might be pertinent to ongoing plots"},
{"Item", "Items", "I", "Items relevant to plots, that is more than just a document saved on a character's own omni-tool"},
{"Document", "Document", "D", "Data files, shadow broker dossiers, and other data that is not inside an item"},
{"Background", "Background", "B", "Rumors, suspicious persons, or inter-RP occurences that may be noticed"},
{"Story", "Stories", "S", "Background stories and inter-RP character intearactions"},
} }
var pageCategories []string var pageCategories []string

25
model/header.go

@ -23,6 +23,17 @@ type Header struct {
PrimaryTag *Tag `json:"primaryTag"` PrimaryTag *Tag `json:"primaryTag"`
} }
// CategoryInfo gets information about the category
func (header *Header) CategoryInfo() PageCategory {
for _, category := range PageCategories {
if category.Key == header.Category {
return category
}
}
return PageCategory{"Unknown", "Unknown", "?", ""}
}
// ListHeaders grabs all the general pages from // ListHeaders grabs all the general pages from
// the database to list them // the database to list them
func ListHeaders() ([]Header, error) { func ListHeaders() ([]Header, error) {
@ -31,7 +42,8 @@ func ListHeaders() ([]Header, error) {
FROM page FROM page
LEFT JOIN page_tag ON (page.id = page_tag.page_id AND page_tag.primary = true) LEFT JOIN page_tag ON (page.id = page_tag.page_id AND page_tag.primary = true)
LEFT JOIN tag ON (tag.id = page_tag.tag_id) LEFT JOIN tag ON (tag.id = page_tag.tag_id)
WHERE page.specific=false AND page.published=true AND page.unlisted=false;
WHERE page.specific=false AND page.published=true AND page.unlisted=false
ORDER BY page.fictional_date DESC
` `
db := server.Main.DB db := server.Main.DB
@ -63,7 +75,8 @@ func ListHeadersByCategory(category string) ([]Header, error) {
FROM page FROM page
LEFT JOIN page_tag ON (page.id = page_tag.page_id AND page_tag.primary = true) LEFT JOIN page_tag ON (page.id = page_tag.page_id AND page_tag.primary = true)
LEFT JOIN tag ON (tag.id = page_tag.tag_id) LEFT JOIN tag ON (tag.id = page_tag.tag_id)
WHERE page.specific=false AND page.published=true AND page.unlisted=false AND page.category = ?;
WHERE page.specific=false AND page.published=true AND page.unlisted=false AND page.category = ?
ORDER BY page.fictional_date DESC
` `
db := server.Main.DB db := server.Main.DB
@ -97,7 +110,8 @@ func ListHeadersByTag(category string, tag *Tag) ([]Header, error) {
RIGHT JOIN page ON page.id = page_tag.page_id RIGHT JOIN page ON page.id = page_tag.page_id
LEFT JOIN (page_tag AS pt2) ON (page.id = pt2.page_id AND pt2.primary = true) LEFT JOIN (page_tag AS pt2) ON (page.id = pt2.page_id AND pt2.primary = true)
LEFT JOIN (tag AS tag) ON (tag.id = pt2.tag_id) LEFT JOIN (tag AS tag) ON (tag.id = pt2.tag_id)
WHERE page_tag.tag_id=?
WHERE page_tag.tag_id=? AND page.unlisted=false AND page.published=true
ORDER BY page.fictional_date DESC
` `
const query2 = ` const query2 = `
SELECT page.id,page.name,page.author,page.category,page.fictional_date,page.publish_date,page.edit_date,page.dated,tag.id,tag.type,tag.name SELECT page.id,page.name,page.author,page.category,page.fictional_date,page.publish_date,page.edit_date,page.dated,tag.id,tag.type,tag.name
@ -105,7 +119,8 @@ func ListHeadersByTag(category string, tag *Tag) ([]Header, error) {
RIGHT JOIN page ON page.id = page_tag.page_id RIGHT JOIN page ON page.id = page_tag.page_id
LEFT JOIN (page_tag AS pt2) ON (page.id = pt2.page_id AND pt2.primary = true) LEFT JOIN (page_tag AS pt2) ON (page.id = pt2.page_id AND pt2.primary = true)
LEFT JOIN (tag AS tag) ON (tag.id = pt2.tag_id) LEFT JOIN (tag AS tag) ON (tag.id = pt2.tag_id)
WHERE page_tag.tag_id=? AND page.category=?
WHERE page_tag.tag_id=? AND page.category=? AND page.unlisted=false AND page.published=true
ORDER BY page.fictional_date DESC
` `
if tag == nil { if tag == nil {
@ -190,7 +205,7 @@ func parseHeader(header *Header, rows *sql.Rows) error {
rows.Scan(&header.ID, &header.Name, &header.Author, &header.Category, &fictionalDate, &publishDate, &editDate, &header.Dated, &tagID, &tagType, &tagName) rows.Scan(&header.ID, &header.Name, &header.Author, &header.Category, &fictionalDate, &publishDate, &editDate, &header.Dated, &tagID, &tagType, &tagName)
if tagID != "" { if tagID != "" {
header.PrimaryTag = &Tag{tagID, tagName, tagType}
header.PrimaryTag = &Tag{tagID, tagType, tagName}
} }
header.FictionalDate, err = time.Parse("2006-01-02 15:04:05", fictionalDate) header.FictionalDate, err = time.Parse("2006-01-02 15:04:05", fictionalDate)

40
model/page.go

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"html/template"
"net/url" "net/url"
"time" "time"
@ -46,6 +47,7 @@ type Page struct {
prevTags []Tag prevTags []Tag
cachedOutput string cachedOutput string
primaryTag *Tag
} }
// Defaults fills in the default details for a page, suited for populating a form // Defaults fills in the default details for a page, suited for populating a form
@ -99,6 +101,10 @@ func (page *Page) Insert() error {
} }
} }
if len(page.Tags) > 0 {
page.primaryTag = &page.Tags[0]
}
return nil return nil
} }
@ -157,6 +163,9 @@ func (page *Page) Update() error {
return err return err
} }
} }
if len(page.Tags) > 0 {
page.primaryTag = &page.Tags[0]
}
return nil return nil
} }
@ -184,29 +193,33 @@ func (page *Page) Delete() error {
} }
// Content parses the content of the page // Content parses the content of the page
func (page *Page) Content() (string, error) {
if page.cachedOutput != "" {
return page.cachedOutput, nil
}
func (page *Page) Content() (template.HTML, error) {
if page.Type == "Markdown" { if page.Type == "Markdown" {
// TODO: Convert [[Ehanis Tioran]] to [Ehanis Tioran](https://wiki.aiterp.net/index.php?title=Ehanis%20Tioran) // TODO: Convert [[Ehanis Tioran]] to [Ehanis Tioran](https://wiki.aiterp.net/index.php?title=Ehanis%20Tioran)
unsafe := blackfriday.MarkdownCommon([]byte(page.Source)) unsafe := blackfriday.MarkdownCommon([]byte(page.Source))
page.cachedOutput = string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
output := string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
return page.cachedOutput, nil
return template.HTML(output), nil
} }
return "", fmt.Errorf("Page type '%s' is not supported", page.Type) return "", fmt.Errorf("Page type '%s' is not supported", page.Type)
} }
// PrimaryTag gets the page's primary tag
func (page *Page) PrimaryTag() Tag {
if page.primaryTag == nil {
return Tag{}
}
return *page.primaryTag
}
// ParseForm validates the values in a form and sets the page's values whenever possible regardless // ParseForm validates the values in a form and sets the page's values whenever possible regardless
// so that it can be pushed to the viewmodel to allow the user to correct their mistakes without fear // so that it can be pushed to the viewmodel to allow the user to correct their mistakes without fear
// of losing their hard work // of losing their hard work
func (page *Page) ParseForm(form url.Values) []error { func (page *Page) ParseForm(form url.Values) []error {
errors := make([]error, 0, 4) errors := make([]error, 0, 4)
page.cachedOutput = ""
err := formparser.String(form.Get("name"), &page.Name, 2, 192) err := formparser.String(form.Get("name"), &page.Name, 2, 192)
if err != nil { if err != nil {
@ -266,10 +279,11 @@ func FindPage(id string) (*Page, error) {
WHERE id=? WHERE id=?
` `
const selectPageTags = ` const selectPageTags = `
SELECT tag.id,tag.type,tag.name
SELECT tag.id,tag.type,tag.name,page_tag.primary
FROM page_tag FROM page_tag
RIGHT JOIN tag ON (tag.id = page_tag.tag_id) RIGHT JOIN tag ON (tag.id = page_tag.tag_id)
WHERE page_tag.page_id = ? WHERE page_tag.page_id = ?
ORDER BY tag.type DESC, tag.name
` `
db := server.Main.DB db := server.Main.DB
@ -297,9 +311,15 @@ func FindPage(id string) (*Page, error) {
page.Tags = make([]Tag, 0, 64) page.Tags = make([]Tag, 0, 64)
for rows.Next() { for rows.Next() {
primary := false
tag := Tag{} tag := Tag{}
rows.Scan(&tag.ID, &tag.Type, &tag.Name)
rows.Scan(&tag.ID, &tag.Type, &tag.Name, &primary)
page.Tags = append(page.Tags, tag) page.Tags = append(page.Tags, tag)
if primary {
page.primaryTag = &tag
}
} }
return page, nil return page, nil

2
model/page_test.go

@ -259,7 +259,7 @@ func TestPage(t *testing.T) {
t.Errorf("page.Content: %s", err) t.Errorf("page.Content: %s", err)
} }
assertEquals(t, "page.Content()", content, "<h1>Returning Va’ynna’s Omni-Tool</h1>\n\n<p>Additional Content is additional</p>\n")
assertEquals(t, "page.Content()", string(content), "<h1>Returning Va’ynna’s Omni-Tool</h1>\n\n<p>Additional Content is additional</p>\n")
}) })
t.Run("WikiURL", func(t *testing.T) { t.Run("WikiURL", func(t *testing.T) {

12
model/tag.go

@ -2,6 +2,7 @@ package model
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
"git.aiterp.net/AiteRP/aitestory/server" "git.aiterp.net/AiteRP/aitestory/server"
@ -24,6 +25,17 @@ type Tag struct {
Name string Name string
} }
// Icon is the API used to get the icon for the tag.
func (tag Tag) Icon() string {
return tag.Type[0:1]
}
// CSSCLass gets the CSS class that colors the tag on the
// page list
func (tag Tag) CSSCLass() string {
return fmt.Sprintf("ttype-%s", strings.ToLower(tag.Type))
}
// Insert adds the tag to the database, giving it a new unique ID // Insert adds the tag to the database, giving it a new unique ID
func (tag *Tag) Insert() error { func (tag *Tag) Insert() error {
db := server.Main.DB db := server.Main.DB

28
view/funcs.go

@ -0,0 +1,28 @@
package view
import (
"html/template"
"strings"
"time"
)
func formatDate(date time.Time) string {
return date.Format("Jan _2, 2006")
}
func formatDateLong(date time.Time) string {
return date.Format("Jan _2, 2006 15:04 MST")
}
func formatUserID(userid string) string {
split := strings.SplitN(userid, ":", 2)
return split[len(split)-1]
}
var funcMap = template.FuncMap{
"formatDate": formatDate,
"formatDateLong": formatDateLong,
"formatUserID": formatUserID,
"tolower": strings.ToLower,
}

11
view/renderer.go

@ -27,7 +27,7 @@ func Register(name string, base string, fragments ...string) {
} }
args := append([]string{path.Join(rootPath, name+".tmpl"), path.Join(rootPath, base+".tmpl")}, fragments...) args := append([]string{path.Join(rootPath, name+".tmpl"), path.Join(rootPath, base+".tmpl")}, fragments...)
tmpl, err := template.New(name).ParseFiles(args...)
tmpl, err := template.New(name).Funcs(funcMap).ParseFiles(args...)
if err != nil { if err != nil {
log.Fatalf("Failed to register %s: %s", name, err) log.Fatalf("Failed to register %s: %s", name, err)
} }
@ -44,7 +44,7 @@ func Render(w http.ResponseWriter, name string, status int, viewModel interface{
var err error var err error
if server.Main.Config.Server.Debug { if server.Main.Config.Server.Debug {
tmpl, err = template.New(name).ParseFiles(argsCache[name]...)
tmpl, err = template.New(name).Funcs(funcMap).ParseFiles(argsCache[name]...)
if err != nil { if err != nil {
response.Text(w, 500, "Failed to run template "+name+": "+err.Error()) response.Text(w, 500, "Failed to run template "+name+": "+err.Error())
return return
@ -78,6 +78,9 @@ func Run(w io.Writer, name string, viewModel interface{}) error {
func init() { func init() {
Register("index", "base/default") Register("index", "base/default")
Register("login", "base/default")
Register("create", "base/default")
Register("user/login", "base/default")
Register("page/create", "base/default")
Register("page/edit", "base/default")
Register("page/view", "base/default")
Register("page/delete", "base/default")
} }

32
view/templates/index.tmpl

@ -1,6 +1,28 @@
{{ define "content" }} {{ define "content" }}
<article> <article>
<table class="page-list">
{{ range .Headers }}
<tbody>
<tr class="">
<td class="pl-icon">{{.CategoryInfo.Icon}}</td>
<td class="pl-content">
<div class="plc-title"><a href="/page/{{.ID}}">{{.Name}}</a></div>
<div class="plc-meta">
{{ if .Dated }}
<div class="plcm-date">{{.FictionalDate | formatDate}}</div>
{{ else }}
<div class="plcm-date">{{.PublishDate | formatDate}}</div>
{{ end }}
{{ if .PrimaryTag }}
<div class="plcm-tag {{.PrimaryTag.CSSCLass}}">{{.PrimaryTag.Name}}</div>
{{ end }}
<div class="plcm-author">{{.Author | formatUserID}}</div>
</div>
</td>
</tr>
<tr class="spacer"><td></td></tr>
{{ end }}
</table>
</article> </article>
{{ end }} {{ end }}
@ -9,10 +31,16 @@
<ul> <ul>
{{ range .Categories }} {{ range .Categories }}
<li class="{{ if eq .Key $.ActiveCategory.Key }}selected{{end}}"><a href="/{{.URLRoot}}/{{$.ActiveTag.Hook}}"><div class="mg-icon">Π</div><div class="mg-label">{{.Plural}}</div></a></li>
<li class="{{ if eq .Key $.ActiveCategory.Key }}selected{{end}}"><a href="/{{.URLRoot}}/"><div class="mg-icon">{{.Icon}}</div><div class="mg-label">{{.Plural}}</div></a></li>
{{ end }} {{ end }}
</ul> </ul>
{{ if $.ActiveTag.ID }}
<ul>
<li class="selected"><a href="/{{$.ActiveTag.Hook}}"><div class="mg-icon">{{$.ActiveTag.Icon}}</div><div class="mg-label">{{$.ActiveTag.Name}}</div></a></li>
</ul>
{{ end }}
{{ if $.User.LoggedIn }} {{ if $.User.LoggedIn }}
<ul> <ul>
<li><a href="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li> <li><a href="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li>

12
view/templates/create.tmpl → view/templates/page/create.tmpl

@ -1,11 +1,11 @@
{{ define "content" }} {{ define "content" }}
<article> <article>
<h1>Create</h1>
<h1>{{$.Operation}}</h1>
<form action="/page/create", method="POST"> <form action="/page/create", method="POST">
<p class="red">{{$.Error}}</p>
<input class="big" placeholder="Page Name" name="name" type="text" value="{{$.Page.Name}}" autofocus />
<p class="danger">{{$.Error}}</p>
<input class="big" placeholder="Page Name" name="name" type="text" value="{{$.Page.Name}}" />
<textarea class="tall" placeholder="Content" name="source">{{$.Page.Source}}</textarea> <textarea class="tall" placeholder="Content" name="source">{{$.Page.Source}}</textarea>
<input placeholder="IC Date" name="fictionalDate" type="text" value="{{if $.Page.FictionalDate.IsZero}}{{else}}{{$.Page.FictionalDate}}{{end}}" autofocus />
<input placeholder="IC Date" name="fictionalDate" type="text" value="{{if $.Page.FictionalDate.IsZero}}{{else}}{{$.Page.FictionalDate}}{{end}}" />
<div class="group"> <div class="group">
{{ range $.Categories }} {{ range $.Categories }}
<div class="radio-wrapper"> <div class="radio-wrapper">
@ -13,7 +13,7 @@
</div> </div>
{{ end }} {{ end }}
</div> </div>
<textarea name="tags" placeholder="Tags, e.g. 'Location: Miner's Respite'. One per line, topmost is primary tag"></textarea>
<textarea name="tags" placeholder="Tags, e.g. 'Location: Miner's Respite'. One per line, topmost is primary tag">{{$.TagInput}}</textarea>
<div class="group"> <div class="group">
<div class="radio-wrapper"> <div class="radio-wrapper">
<input type="checkbox" name="dated" value="true" {{if $.Page.Dated}}checked{{end}}><b> Dated</b>: The IC date is shown on the page list. It will still be used for sorting if this option is disabled.</input> <input type="checkbox" name="dated" value="true" {{if $.Page.Dated}}checked{{end}}><b> Dated</b>: The IC date is shown on the page list. It will still be used for sorting if this option is disabled.</input>
@ -30,7 +30,7 @@
<input type="hidden" name="type" value="Markdown" /> <input type="hidden" name="type" value="Markdown" />
<input type="hidden" name="published" value="True" /> <input type="hidden" name="published" value="True" />
<button type="submit">Submit</button>
<button type="submit">{{$.Operation}}</button>
</form> </form>
</article> </article>

37
view/templates/page/delete.tmpl

@ -0,0 +1,37 @@
{{ define "content" }}
<article class="narrow">
<h1 class="danger">Delete Page</h1>
<form action="/page/delete/{{$.Page.ID}}", method="POST">
<p class="danger">
This is an irreversible action, so make sure that this is the correct page!
</p>
<ul>
<li><b>ID:</b> {{$.Page.ID}}</li>
<li><b>Name:</b> {{$.Page.Name}}</li>
<li><b>Category:</b> {{$.Page.Category}}</li>
<li><b>Published:</b> {{$.Page.PublishDate | formatDateLong}}</li>
</ul>
<input type="hidden" name="aft" value="{{$.User.SessionID}}" />
<button type="submit">Delete Page</button>
</article>
{{ end }}
{{ define "menu" }}
<a href="/"><h1>Page</h1></a>
{{ if eq $.User.ID $.Page.Author}}
<ul>
<li><a href="/page/edit/{{$.Page.ID}}"><div class="mg-icon">E</div><div class="mg-label">Edit</div></a></li>
<li class="selected"><a href="/page/delete/{{$.Page.ID}}"><div class="mg-icon">X</div><div class="mg-label">Delete</div></a></li>
</ul>
{{ end }}
<ul>
<li><a href="/page/{{$.Page.ID}}"><div class="mg-icon">&lt;</div><div class="mg-label">Back</div></a></li>
</ul>
{{ end }}
{{ define "head" }}
<link rel="stylesheet" href="/ui/css/form.css" />
{{ end }}

50
view/templates/page/edit.tmpl

@ -0,0 +1,50 @@
{{ define "content" }}
<article>
<h1>{{$.Operation}}</h1>
<form action="/page/edit/{{$.Page.ID}}", method="POST">
<p class="danger">{{$.Error}}</p>
<input class="big" placeholder="Page Name" name="name" type="text" value="{{$.Page.Name}}" />
<textarea class="tall" placeholder="Content" name="source">{{$.Page.Source}}</textarea>
<input placeholder="IC Date" name="fictionalDate" type="text" value="{{if $.Page.FictionalDate.IsZero}}{{else}}{{$.Page.FictionalDate}}{{end}}" />
<div class="group">
{{ range $.Categories }}
<div class="radio-wrapper">
<input name="category" type="radio" name="category" value="{{.Key}}" {{if eq $.Page.Category .Key}}checked{{end}}><b> {{.Key}}</b>: {{.Info}}</input>
</div>
{{ end }}
</div>
<textarea name="tags" placeholder="Tags, e.g. 'Location: Miner's Respite'. One per line, topmost is primary tag">{{$.TagInput}}</textarea>
<div class="group">
<div class="radio-wrapper">
<input type="checkbox" name="dated" value="true" {{if $.Page.Dated}}checked{{end}}><b> Dated</b>: The IC date is shown on the page list. It will still be used for sorting if this option is disabled.</input>
</div>
<div class="radio-wrapper">
<input type="checkbox" name="unlisted" value="true" {{if $.Page.Unlisted}}checked{{end}}><b> Unlisted</b>: This page will not show up on page lists, but anyone with a link can view it.</input>
</div>
<div class="radio-wrapper">
<input type="checkbox" name="specific" value="true" {{if $.Page.Specific}}checked{{end}}><b> Specific</b>: This page will only show up on page lists when one of its tags is searched for.</input>
</div>
</div>
<!-- Future option -->
<input type="hidden" name="type" value="Markdown" />
<input type="hidden" name="published" value="True" />
<button type="submit">{{$.Operation}}</button>
</form>
</article>
{{ end }}
{{ define "menu" }}
<a href="/"><h1>Aite RP</h1></a>
<ul>
<li><a href="/"><div class="mg-icon">&lt;</div><div class="mg-label">Back</div></a></li>
</ul>
{{ end }}
{{ define "head" }}
<link rel="stylesheet" href="/ui/css/form.css" />
<script type="text/javascript" src="/ui/js/form-page.js"></script>
{{ end }}

44
view/templates/page/view.tmpl

@ -0,0 +1,44 @@
{{ define "content" }}
<article class="narrow">
{{$.Page.Content}}
</article>
{{ end }}
{{ define "menu" }}
<a href="/page/{{$.Page.ID}}"><h1>Page</h1></a>
{{ if $.Page.Dated}}
<div class="page-property">{{$.Page.FictionalDate | formatDate}}</div>
{{ else }}
<div class="page-property">{{$.Page.PublishDate | formatDate}}</div>
{{ end }}
<ul>
{{ range $.Page.Tags }}
<li><a href="/{{.Hook}}"><div class="mg-icon">{{.Icon}}</div><div class="mg-label {{.CSSCLass}}">{{.Name}}</div></a></li>
{{ end }}
</ul>
{{ if eq $.User.ID $.Page.Author}}
<ul>
<li><a href="/page/edit/{{$.Page.ID}}"><div class="mg-icon">E</div><div class="mg-label">Edit</div></a></li>
<li><a href="/page/delete/{{$.Page.ID}}"><div class="mg-icon">X</div><div class="mg-label">Delete</div></a></li>
</ul>
{{ end }}
<ul>
<li><a href="/"><div class="mg-icon">&lt;</div><div class="mg-label">Back</div></a></li>
</ul>
{{ if $.User.LoggedIn }}
<ul>
<li><a href="/user/logout"><div class="mg-icon">A</div><div class="mg-label">Logout</div></a></li>
</ul>
{{ else }}
<ul>
<li><a href="/user/login"><div class="mg-icon">A</div><div class="mg-label">Login</div></a></li>
</ul>
{{ end }}
{{ end }}
{{ define "head" }}
{{ end }}

3
view/templates/login.tmpl → view/templates/user/login.tmpl

@ -2,7 +2,8 @@
<article> <article>
<h1>Login</h1> <h1>Login</h1>
<form action="/user/login", method="POST"> <form action="/user/login", method="POST">
<p class="red">{{$.Error}}</p>
<p>Use your wiki.aiterp.net account.</p>
<p class="danger">{{$.Error}}</p>
<input placeholder="Username" name="username" type="text" value="{{$.UserName}}" {{if ne $.UserName ""}}autofocus{{end}} /> <input placeholder="Username" name="username" type="text" value="{{$.UserName}}" {{if ne $.UserName ""}}autofocus{{end}} />
<input placeholder="Password" name="password" type="password" {{if $.UserName}}autofocus{{end}} /> <input placeholder="Password" name="password" type="password" {{if $.UserName}}autofocus{{end}} />
<button type="submit">Submit</button> <button type="submit">Submit</button>

10
viewmodel/base.go

@ -11,10 +11,11 @@ import (
// Base is the basic information used to render the page // Base is the basic information used to render the page
type Base struct { type Base struct {
User struct { User struct {
ID string
Name string
Role string
LoggedIn bool
ID string
Name string
Role string
LoggedIn bool
SessionID string
} }
ViewTitle string ViewTitle string
} }
@ -28,6 +29,7 @@ func (base *Base) setupBase(user *auth.User, viewTitle string) {
base.User.Name = user.ID base.User.Name = user.ID
base.User.Role = user.Data["role"] base.User.Role = user.Data["role"]
base.User.LoggedIn = true base.User.LoggedIn = true
base.User.SessionID = user.Session.ID
} }
base.ViewTitle = fmt.Sprintf("%s - %s", viewTitle, server.Main.Config.View.Title) base.ViewTitle = fmt.Sprintf("%s - %s", viewTitle, server.Main.Config.View.Title)

2
viewmodel/pageview.go

@ -7,7 +7,7 @@ import (
type PageView struct { type PageView struct {
Base Base
Page model.Page
Page *model.Page
} }
func (pv *PageView) Setup(user *auth.User) { func (pv *PageView) Setup(user *auth.User) {

Loading…
Cancel
Save