Browse Source

Massive overhaul, fixing menus and changing template engine. Added /month/ and /author/ filters

master
Gisle Aune 7 years ago
parent
commit
fb9e43ba12
  1. 94
      controllers/listcontroller.go
  2. 44
      controllers/pagecontroller.go
  3. 34
      controllers/tagcontroller.go
  4. 19
      main.go
  5. 11
      model/category.go
  6. 74
      model/header.go
  7. 7
      model/page.go
  8. 21
      model/tag.go
  9. 44
      view/amber/base/master.amber
  10. 30
      view/amber/base/mixins.amber
  11. 63
      view/amber/create.amber
  12. 25
      view/amber/index.amber
  13. 25
      view/amber/menu.amber
  14. 12
      view/amber/message.amber
  15. 30
      view/amber/page/delete.amber
  16. 64
      view/amber/page/edit.amber
  17. 29
      view/amber/page/menu.amber
  18. 14
      view/amber/page/source.amber
  19. 13
      view/amber/page/view.amber
  20. 17
      view/amber/tags.amber
  21. 26
      view/amber/user/login.amber
  22. 4
      view/amber/user/menu.amber
  23. 79
      view/renderer.go
  24. 41
      view/templates/base/default.tmpl
  25. 66
      view/templates/index.tmpl
  26. 44
      view/templates/page/create.tmpl
  27. 37
      view/templates/page/delete.tmpl
  28. 44
      view/templates/page/edit.tmpl
  29. 44
      view/templates/page/view.tmpl
  30. 48
      view/templates/tags/list.tmpl
  31. 24
      view/templates/user/login.tmpl
  32. 9
      viewmodel/base.go
  33. 16
      viewmodel/indexbase.go
  34. 12
      viewmodel/indexlist.go
  35. 49
      viewmodel/indextags.go
  36. 19
      viewmodel/message.go
  37. 9
      viewmodel/pagebase.go
  38. 26
      viewmodel/pagecreate.go
  39. 19
      viewmodel/pagedelete.go
  40. 14
      viewmodel/pageedit.go
  41. 20
      viewmodel/pagesource.go
  42. 11
      viewmodel/pageview.go
  43. 46
      viewmodel/taglist.go
  44. 2
      viewmodel/userlogin.go

94
controllers/listcontroller.go

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
"time"
"git.aiterp.net/gisle/wrouter/response" "git.aiterp.net/gisle/wrouter/response"
@ -24,10 +25,10 @@ func listIndex(path string, w http.ResponseWriter, req *http.Request, user *auth
return false return false
} }
vm := viewmodel.PageList{}
vm := viewmodel.IndexList{}
vm.Headers, err = model.ListHeaders() vm.Headers, err = model.ListHeaders()
vm.Categories = model.PageCategories vm.Categories = model.PageCategories
vm.Setup(user)
vm.Setup(user, req.URL.Path, "Index")
if err != nil { if err != nil {
response.Text(w, 500, err.Error()) response.Text(w, 500, err.Error())
@ -49,7 +50,7 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
tagName := strings.Replace(req.URL.Path[len(path):], "_", " ", -1) tagName := strings.Replace(req.URL.Path[len(path):], "_", " ", -1)
vm := viewmodel.PageList{}
vm := viewmodel.IndexList{}
if tagName != "" { if tagName != "" {
tag, err := model.FindTag("name", tagName, "") tag, err := model.FindTag("name", tagName, "")
@ -66,7 +67,7 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
vm.Categories = model.PageCategories vm.Categories = model.PageCategories
vm.ActiveCategory = category vm.ActiveCategory = category
vm.Setup(user)
vm.Setup(user, path, category.Plural)
if err != nil { if err != nil {
response.Text(w, 500, err.Error()) response.Text(w, 500, err.Error())
@ -89,7 +90,7 @@ func listTagged(tagType string) wrouter.FunctionHandlerFunc {
tagName := strings.Replace(req.URL.Path[len(path):], "_", " ", -1) tagName := strings.Replace(req.URL.Path[len(path):], "_", " ", -1)
vm := viewmodel.PageList{}
vm := viewmodel.IndexList{}
if tagName == "" { if tagName == "" {
return false return false
@ -105,7 +106,7 @@ func listTagged(tagType string) wrouter.FunctionHandlerFunc {
vm.Headers, err = model.ListHeadersByTag("", tag) vm.Headers, err = model.ListHeadersByTag("", tag)
vm.Categories = model.PageCategories vm.Categories = model.PageCategories
vm.Setup(user)
vm.Setup(user, tag.Path(), tag.Name)
if err != nil { if err != nil {
response.Text(w, 500, err.Error()) response.Text(w, 500, err.Error())
@ -118,11 +119,90 @@ func listTagged(tagType string) wrouter.FunctionHandlerFunc {
} }
} }
func tagList(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
//var err error
if (req.Method != "GET" && req.Method != "POST") || len(req.URL.Path) > len(path) {
return false
}
tl := viewmodel.IndexTags{}
tl.Tags, _ = model.ListTags()
tl.Setup(user, path)
view.Render(w, "tags", 200, tl)
return true
}
func listAuthor(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
var err error
if req.Method != "GET" || strings.LastIndex(req.URL.Path, "/") >= len(path) {
return false
}
authorName := strings.Replace(req.URL.Path[len(path):], "_", " ", -1)
vm := viewmodel.IndexList{}
vm.ActiveAuthor = authorName
vm.Headers, err = model.ListHeadersByAuthor(authorName)
if err != nil {
response.Text(w, 500, err.Error())
return true
}
if len(vm.Headers) > 0 {
authorName = vm.Headers[0].Author
}
vm.Categories = model.PageCategories
vm.Setup(user, "/author/"+authorName, authorName)
view.Render(w, "index", 200, vm)
return true
}
func listDate(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
var err error
if req.Method != "GET" || strings.LastIndex(req.URL.Path, "/") >= len(path) {
return false
}
date, err := time.Parse("2006-01", req.URL.Path[len(path):])
if err != nil {
response.Text(w, 400, "Invalid date")
return true
}
vm := viewmodel.IndexList{}
vm.ActiveDate = date.Format("January 2006")
vm.ActiveDatePath = "/month/" + date.Format("2006-01")
vm.Headers, err = model.ListHeadersByMonth(date.Year(), int(date.Month()))
if err != nil {
response.Text(w, 500, err.Error())
return true
}
vm.Categories = model.PageCategories
vm.Setup(user, "/month/"+date.Format("2006-01"), vm.ActiveDate)
view.Render(w, "index", 200, vm)
return true
}
func init() { func init() {
ListController.Function("/", listIndex) ListController.Function("/", listIndex)
ListController.Function("/tags/", tagList)
ListController.Function("/author/", listAuthor)
ListController.Function("/month/", listDate)
for _, category := range model.PageCategories { for _, category := range model.PageCategories {
ListController.Function(fmt.Sprintf("/%s/", strings.ToLower(category.Plural)), listFiltered(category))
ListController.Function(category.Path, listFiltered(category))
} }
for _, tagType := range model.TagTypes { for _, tagType := range model.TagTypes {

44
controllers/pagecontroller.go

@ -25,9 +25,8 @@ func pageCreate(path string, w http.ResponseWriter, req *http.Request, user *aut
return false return false
} }
pc := viewmodel.PageForm{}
pc := viewmodel.PageCreate{}
pc.Setup(user) pc.Setup(user)
pc.Operation = "Create"
// Make sure the user is logged in // Make sure the user is logged in
if user == nil { if user == nil {
@ -110,7 +109,7 @@ func pageCreate(path string, w http.ResponseWriter, req *http.Request, user *aut
pc.TagInput = req.Form.Get("tags") pc.TagInput = req.Form.Get("tags")
} }
view.Render(w, "page/create", 200, pc)
view.Render(w, "create", 200, pc)
return true return true
} }
@ -137,6 +136,28 @@ func pageView(path string, w http.ResponseWriter, req *http.Request, user *auth.
return true return true
} }
func pageSource(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.PageSource{}
pv.Page = page
pv.Setup(user)
view.Render(w, "page/source", 200, pv)
return true
}
func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool { 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
@ -150,12 +171,12 @@ func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *aut
return false return false
} }
pv := viewmodel.PageView{}
pv := viewmodel.PageDelete{}
pv.Page = page pv.Page = page
pv.Setup(user) pv.Setup(user)
if user == nil || user.FullID() != page.Author { if user == nil || user.FullID() != page.Author {
view.Render(w, "message/error-access", http.StatusForbidden, path)
view.Render(w, "message", http.StatusForbidden, viewmodel.NewMessage(user, "Access Denied", true, "You are not allowed to delete this page."))
return true return true
} }
@ -164,7 +185,7 @@ func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *aut
// Catch sneaky shenanigans // Catch sneaky shenanigans
if req.Form.Get("aft") != user.Session.ID { if req.Form.Get("aft") != user.Session.ID {
view.Render(w, "message/error-forgery", http.StatusForbidden, path)
view.Render(w, "message", http.StatusForbidden, viewmodel.NewMessage(user, "Forgery Detected", true, "The server thinks something nasty is afoot. Try again if it is your page, and prod me on IRC if it still doesn't work."))
return true return true
} }
@ -172,12 +193,12 @@ func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *aut
err := page.Delete() err := page.Delete()
if err != nil { if err != nil {
// It wasn't done D: // It wasn't done D:
view.Render(w, "message/error-internal", http.StatusInternalServerError, err)
view.Render(w, "message", http.StatusInternalServerError, viewmodel.NewMessage(user, "Internal Server Error", true, err.Error()))
return true return true
} }
// It has been done // It has been done
view.Render(w, "message/page-deleted", 200, pv)
view.Render(w, "message", 200, viewmodel.NewMessage(user, "Deletion Successful", false, "The page has been deleted."))
return true return true
} }
@ -198,12 +219,10 @@ func pageEdit(path string, w http.ResponseWriter, req *http.Request, user *auth.
return false return false
} }
pf := viewmodel.PageForm{}
pf := viewmodel.PageEdit{}
pf.Page = page
pf.Setup(user) pf.Setup(user)
pf.Operation = "Edit"
pf.Page = *page
if user == nil { if user == nil {
pf.Error = "You are not logged in" pf.Error = "You are not logged in"
} }
@ -285,5 +304,6 @@ func init() {
PageController.Function("/create", pageCreate) PageController.Function("/create", pageCreate)
PageController.Function("/edit/", pageEdit) PageController.Function("/edit/", pageEdit)
PageController.Function("/delete/", pageDelete) PageController.Function("/delete/", pageDelete)
PageController.Function("/source/", pageSource)
PageController.Function("/", pageView) PageController.Function("/", pageView)
} }

34
controllers/tagcontroller.go

@ -1,34 +0,0 @@
package controllers
import (
"net/http"
"git.aiterp.net/AiteRP/aitestory/view"
"git.aiterp.net/AiteRP/aitestory/viewmodel"
"git.aiterp.net/AiteRP/aitestory/model"
"git.aiterp.net/gisle/wrouter"
"git.aiterp.net/gisle/wrouter/auth"
)
// TagController serves and handles the tag managment pages
var TagController = wrouter.Router{}
func tagList(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
//var err error
if (req.Method != "GET" && req.Method != "POST") || len(req.URL.Path) > len(path) {
return false
}
tl := viewmodel.TagList{}
tl.Tags, _ = model.ListTags()
tl.Setup(user)
view.Render(w, "tags/list", 200, tl)
return true
}
func init() {
TagController.Function("/", tagList)
}

19
main.go

@ -1,8 +1,14 @@
package main package main
import "git.aiterp.net/AiteRP/aitestory/server"
import "git.aiterp.net/AiteRP/aitestory/controllers"
import "git.aiterp.net/gisle/wrouter/auth"
import (
"net/http"
"git.aiterp.net/AiteRP/aitestory/controllers"
"git.aiterp.net/AiteRP/aitestory/server"
"git.aiterp.net/AiteRP/aitestory/view"
"git.aiterp.net/AiteRP/aitestory/viewmodel"
"git.aiterp.net/gisle/wrouter/auth"
)
func main() { func main() {
router := &server.Main.Router router := &server.Main.Router
@ -10,10 +16,15 @@ func main() {
auth.Register(&controllers.WikiAthenticator{}) auth.Register(&controllers.WikiAthenticator{})
router.Mount("/user", &controllers.UserController) router.Mount("/user", &controllers.UserController)
router.Mount("/page", &controllers.PageController) router.Mount("/page", &controllers.PageController)
router.Mount("/tags", &controllers.TagController)
router.Mount("/", &controllers.ListController) router.Mount("/", &controllers.ListController)
router.Static("/ui/", server.Main.Config.Directories.UI) router.Static("/ui/", server.Main.Config.Directories.UI)
router.Function("/", func(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
view.Render(w, "message", http.StatusNotFound, viewmodel.NewMessage(user, "404: "+req.URL.Path, true, "The server don't know what to do with that address."))
return true
})
server.Main.Start() server.Main.Start()
} }

11
model/category.go

@ -9,6 +9,7 @@ type PageCategory struct {
Key string Key string
Plural string Plural string
Icon string Icon string
Path string
Info string Info string
} }
@ -22,12 +23,12 @@ func (category *PageCategory) URLRoot() string {
// 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", "O", "OoC content is for announcements, scheduling, general information, or anything that is not in-universe"}, //{"OoC", "OoC", "O", "OoC content is for announcements, scheduling, general information, or anything that is not in-universe"},
{"Info", "Info", "i", "Information relevant to RP that might be something looked for in IRC logs"},
{"News", "News", "N", "News stories that might be pertinent to ongoing plots"},
{"Info", "Info", "i", "/info/", "Information relevant to RP that might be something looked for in IRC logs"},
{"News", "News", "N", "/news/", "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"}, //{"Item", "Items", "I", "Items relevant to plots, that is more than just a document saved on a character's own omni-tool"},
{"Document", "Documents", "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"},
{"Document", "Documents", "D", "/documents/", "Data files, shadow broker dossiers, and other data that is not inside an item"},
{"Background", "Background", "B", "/background/", "Rumors, suspicious persons, or inter-RP occurences that may be noticed"},
{"Story", "Stories", "S", "/stories/", "Background stories and inter-RP character intearactions"},
} }
var pageCategories []string var pageCategories []string

74
model/header.go

@ -3,6 +3,7 @@ package model
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"time" "time"
"git.aiterp.net/AiteRP/aitestory/server" "git.aiterp.net/AiteRP/aitestory/server"
@ -34,7 +35,12 @@ func (header *Header) CategoryInfo() PageCategory {
} }
} }
return PageCategory{"Unknown", "Unknown", "?", ""}
return PageCategory{"Unknown", "Unknown", "?", "/", ""}
}
// Path returns the path for viewing the page
func (header *Header) Path() string {
return fmt.Sprintf("/page/%s", header.ID)
} }
// ListHeaders grabs all the general pages from // ListHeaders grabs all the general pages from
@ -104,6 +110,72 @@ func ListHeadersByCategory(category string) ([]Header, error) {
return results, nil return results, nil
} }
// ListHeadersByAuthor grabs all the pages by the given author
func ListHeadersByAuthor(author string) ([]Header, error) {
const query = `
SELECT page.id,page.name,author,category,fictional_date,publish_date,edit_date,tag.id,tag.type,tag.name
FROM page
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)
WHERE page.specific=false AND page.published=true AND page.unlisted=false AND page.author = ?
ORDER BY page.publish_date DESC
`
db := server.Main.DB
rows, err := db.Query(query, author)
if err != nil {
return nil, err
}
defer rows.Close()
results := make([]Header, 0, 64)
header := Header{}
for rows.Next() {
err := parseHeader(&header, rows)
if err != nil {
return nil, err
}
results = append(results, header)
}
return results, nil
}
// ListHeadersByMonth grabs all the pages within the given month
func ListHeadersByMonth(year int, month int) ([]Header, error) {
const query = `
SELECT page.id,page.name,author,category,fictional_date,publish_date,edit_date,tag.id,tag.type,tag.name
FROM page
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)
WHERE page.specific=false AND page.published=true AND page.unlisted=false AND YEAR(fictional_date) = ? AND MONTH(fictional_date) = ?
ORDER BY page.publish_date DESC
`
db := server.Main.DB
rows, err := db.Query(query, year, month)
if err != nil {
return nil, err
}
defer rows.Close()
results := make([]Header, 0, 64)
header := Header{}
for rows.Next() {
err := parseHeader(&header, rows)
if err != nil {
return nil, err
}
results = append(results, header)
}
return results, nil
}
// ListHeadersByTag lists all headers that has the tag. Leave the category empty // ListHeadersByTag lists all headers that has the tag. Leave the category empty
// to not filter by it // to not filter by it
func ListHeadersByTag(category string, tag *Tag) ([]Header, error) { func ListHeadersByTag(category string, tag *Tag) ([]Header, error) {

7
model/page.go

@ -139,7 +139,7 @@ func (page *Page) Update() error {
} }
// Stop now if the tages haven't changed // Stop now if the tages haven't changed
if len(page.prevTags) == len(page.Tags) {
if len(page.Tags) > 0 && len(page.prevTags) == len(page.Tags) {
change := false change := false
for i, tag := range page.prevTags { for i, tag := range page.prevTags {
@ -265,6 +265,11 @@ func (page *Page) ParseForm(form url.Values) []error {
return errors return errors
} }
// OpPath gets a path associated with a specific operation on this page.
func (page *Page) OpPath(op string) string {
return fmt.Sprintf("/page/%s/%s", op, page.ID)
}
// Standardize page ID generation // Standardize page ID generation
func (page *Page) generateID() { func (page *Page) generateID() {
page.ID = generate.FriendlyID(16) page.ID = generate.FriendlyID(16)

21
model/tag.go

@ -18,6 +18,15 @@ var TagTypes = []string{
"Series", "Series",
} }
// TagTitles are the titles to use when listing tags
var TagTitles = map[string]string{
"Location": "Locations",
"Character": "Characters",
"Event": "Plots & Events",
"Organization": "Organizations",
"Series": "Series",
}
// Tag describes a tag // Tag describes a tag
type Tag struct { type Tag struct {
ID string ID string
@ -121,10 +130,14 @@ func (tag *Tag) Validate() error {
return nil return nil
} }
// Hook returns the url friendly name, which is pretty much just
// adding underscores to it.
func (tag Tag) Hook() string {
return strings.Replace(tag.Name, " ", "_", -1)
// Path returns a path used for the tag's list page
func (tag Tag) Path() string {
return fmt.Sprintf("/%s/%s", strings.ToLower(tag.Type), strings.Replace(tag.Name, " ", "_", -1))
}
// CSSClass gets the tag's css class, which determines its color.
func (tag Tag) CSSClass() string {
return fmt.Sprintf("ttype-%s", strings.ToLower(tag.Type))
} }
// FindTag finds a tag by ID. Leave tagtype blank to ignore it. Both key and // FindTag finds a tag by ID. Leave tagtype blank to ignore it. Both key and

44
view/amber/base/master.amber

@ -0,0 +1,44 @@
doctype 5
import mixins
html
head
block meta
meta[name="description"][content="This is a great website"]
meta[charset="UTF-8"]
meta[name="viewport"][content="width=device-width, initial-scale=1.0, maximum-scale=2.0, user-scalable=no"]
meta[name="theme-color"][content="#112233"]
meta[http-equiv="Content-Type"][content="text/html; charset=utf-8"]
block css
link[rel="stylesheet"][href="/ui/css/base.css"]
link[rel="stylesheet"][href="/ui/css/magic.css"]
link[rel="stylesheet"][href="/ui/css/theme.css"]
link[rel="stylesheet"][media="screen"][href="/ui/fonts/source-sans-pro/source-sans-pro.css"][type="text/css"]
block js
script[type="text/javascript"][src="/ui/js/background.js"]
title #{ViewTitle}
body
if ViewBackground != ""
img[id="main-background"][src="/ui/img/bg.png"]
else
img[id="main-background"][src="/ui/img/bg.png"]
div.content-wrapper
nav
block menu
if User.LoggedIn
ul
+menuitem("/user/logout", "U", "Logout")
else
ul
+menuitem("/user/login", "U", "Login")
if ViewPath != "/"
+menuitem("/", "<", "Back")
main
block main

30
view/amber/base/mixins.amber

@ -0,0 +1,30 @@
$root = $
mixin menuitem($path, $icon, $text)
li
.selected ? $root.ViewPath == $path
a[href=$path]
div.mg-icon #{$icon}
div.mg-label #{$text}
mixin tagmenuitem($tag)
li
[class=$tag.CSSClass]
a[href=$tag.Path]
div.mg-icon #{$tag.Icon}
div.mg-label #{$tag.Name}
mixin formoption($name, $selected, $label, $info)
div.radio-wrapper
$id = printf("checkbox-%s", $name)
if $selected
input[id=$id][type="checkbox"][name=$name][value="true"][checked]
else
input[id=$id][type="checkbox"][name=$name][value="true"]
label[for="checkbox-unlisted"]
b #{$label}:
spamn #{$info}

63
view/amber/create.amber

@ -0,0 +1,63 @@
extends base/master
block append css
link[rel="stylesheet"][href="/ui/css/form.css"]
block append js
script[type="text/javascript"][src="/ui/js/form-page.js"]
block menu
import menu
block main
$page = $.Page
article
h1 Create
form[action="/page/create"][method="POST"]
p.danger #{$.Error}
input.big
[placeholder="Page Name"]
[value=$page.Name]
[type="text"]
[name="name"]
textarea.tall
[placeholder="Content"]
[name="source"]
| #{$page.Source}
input
[placeholder="IC Date (e.g. 'Oct 27, 2185')"]
[name="fictionalDate"]
[type="text"]
[value=$page.FictionalDate] ? !$page.FictionalDate.IsZero
h2 Category
div.group[title="Category"]
each $category in $.Categories
div.radio-wrapper
$radioid = printf("radio-%s", $category.Key)
if $category.Key == $page.Category
input[id=$radioid][name="category"][type="radio"][name="category"][value=$category.Key][checked]
else
input[id=$radioid][name="category"][type="radio"][name="category"][value=$category.Key]
label[for=$radioid]
b #{$category.Key}:
span #{$category.Info}
h2 Tags
textarea
[name="tags"]
[placeholder="Location: Miner's Respite\nOrganization: Redrock Agency\nCharacter: Renala T'Iavay\nEvent: Skipping Work\nSeries: Just Another Tuesday"]
| #{$.TagInput}
h2 Options
div.group
+formoption("unlisted", $page.Unlisted, "Unlisted", "The page will not show up in any page list. Meant for omni-tool messages, quick copypastes and so on.")
+formoption("indexed", $page.Unlisted, "Indexable", "Third-party search engines (that play by the rules) are permitted to crawl this page.")
// Future options (maybe)
input[type="hidden"][name="type"][value="Markdown"]
input[type="hidden"][name="published"][value="True"]
button[type="submit"] Create

25
view/amber/index.amber

@ -0,0 +1,25 @@
extends base/master
block menu
import menu
block main
article
table.page-list
tbody
each $header in Headers
tr
td.pl-icon #{$header.CategoryInfo.Icon}
td.pl-content
div.plc-title
a[href=$header.Path] #{$header.Name}
div.plc-meta
div.plcm-date #{formatDate($header.PublishDate)}
if $header.Dated
div.plcm-date #{formatDate($header.FictionalDate)}
if $header.PrimaryTag
div.plcm-tag[class=$header.PrimaryTag.CSSCLass] #{$header.PrimaryTag.Name}
div.plcm-author #{formatUserID($header.Author)}
tr.spacer
td

25
view/amber/menu.amber

@ -0,0 +1,25 @@
import base/mixins
$authorPath = printf("/author/%s", ActiveAuthor)
a[href="/"]
h1 Aite RP
ul
if User.LoggedIn
+menuitem("/page/create", "+", "Create")
ul
each $category in Categories
+menuitem($category.Path, $category.Icon, $category.Plural)
ul
if ActiveTag.ID
+menuitem(ActiveTag.Path, ActiveTag.Icon, ActiveTag.Name)
if ActiveDate
+menuitem(ActiveDatePath, "D", ActiveDate)
if ActiveAuthor
+menuitem($authorPath, "A", formatUserID(ActiveAuthor))
ul
+menuitem("/tags/", "T", "Tags")

12
view/amber/message.amber

@ -0,0 +1,12 @@
extends base/master
block menu
a[href="/"]
h1 Aite RP
block main
article
h1
.danger ? $.Danger
| #{$.Title}
p #{$.Text}

30
view/amber/page/delete.amber

@ -0,0 +1,30 @@
extends ../base/master
block append css
link[rel="stylesheet"][href="/ui/css/form.css"]
block append js
script[type="text/javascript"][src="/ui/js/form-page.js"]
block menu
import menu
block main
$page = $.Page
article
h1.danger Delete
form[action=$page.OpPath("delete")][method="POST"]
p.danger This is irreversible, so make sure you are deleting the right page.
ul
li <b>ID:</b> #{$.Page.ID}
li <b>Name:</b> #{$.Page.Name}
li <b>Category:</b> #{$.Page.Category}
li <b>Published:</b> #{formatDateLong($.Page.PublishDate)}
ul
input[type="hidden"][name="aft"][value=$.User.SessionID]
button[type="submit"] Delete

64
view/amber/page/edit.amber

@ -0,0 +1,64 @@
extends ../base/master
block append css
link[rel="stylesheet"][href="/ui/css/form.css"]
block append js
script[type="text/javascript"][src="/ui/js/form-page.js"]
block menu
import menu
block main
$page = $.Page
article
h1 Create
form[action=$page.OpPath("edit")][method="POST"]
p.danger #{$.Error}
input.big
[placeholder="Page Name"]
[value=$page.Name]
[type="text"]
[name="name"]
textarea.tall
[placeholder="Content"]
[name="source"]
| #{$page.Source}
input
[placeholder="IC Date (e.g. 'Oct 27, 2185')"]
[name="fictionalDate"]
[type="text"]
[value=$page.FictionalDate] ? !$page.FictionalDate.IsZero
h2 Category
div.group[title="Category"]
each $category in $.Categories
div.radio-wrapper
$radioid = printf("radio-%s", $category.Key)
if $category.Key == $page.Category
input[id=$radioid][name="category"][type="radio"][name="category"][value=$category.Key][checked]
else
input[id=$radioid][name="category"][type="radio"][name="category"][value=$category.Key]
label[for=$radioid]
b #{$category.Key}:
span #{$category.Info}
h2 Tags
textarea
[name="tags"]
[placeholder="Location: Miner's Respite\nOrganization: Redrock Agency\nCharacter: Renala T'Iavay\nEvent: Skipping Work\nSeries: Just Another Tuesday"]
| #{$.TagInput}
h2 Options
div.group
+formoption("unlisted", $page.Unlisted, "Unlisted", "The page will not show up in any page list. Meant for omni-tool messages, quick copypastes and so on.")
+formoption("indexed", $page.Indexed, "Indexable", "Third-party search engines (that play by the rules) are permitted to crawl this page.")
// Future options (maybe)
input[type="hidden"][name="type"][value="Markdown"]
input[type="hidden"][name="published"][value="True"]
button[type="submit"] Edit

29
view/amber/page/menu.amber

@ -0,0 +1,29 @@
import ../base/mixins
$pagePath = printf("/page/%s", $.Page.ID)
$pageEditPath = printf("/page/edit/%s", $.Page.ID)
$pageDeletePath = printf("/page/delete/%s", $.Page.ID)
$pageSourcePath = printf("/page/source/%s", $.Page.ID)
$datePath = printf("/month/%s", $.Page.FictionalDate.UTC.Format("2006-01"))
$authorPath = printf("/author/%s", $.Page.Author)
a[href="/"]
h1 Page
if !$.HideMenu
ul
+menuitem($pagePath, "V", "View")
if $.Page.Author == $.User.ID
+menuitem($pageEditPath, "E", "Edit")
+menuitem($pageDeletePath, "X", "Delete")
else
+menuitem($pageSourcePath, "#", "Source")
ul
each $tag in $.Page.Tags
+tagmenuitem($tag)
ul
if $.Page.Dated
+menuitem($datePath, "D", formatDate($.Page.FictionalDate))
+menuitem($authorPath, "A", formatUserID($.Page.Author))

14
view/amber/page/source.amber

@ -0,0 +1,14 @@
extends ../base/master
block append meta
if !$.Page.Indexed
meta[name="robots"][content="noindex"]
meta[name="googlebot"][content="noindex"]
block menu
import menu
block main
article
code.block
#{$.Page.Source}

13
view/amber/page/view.amber

@ -0,0 +1,13 @@
extends ../base/master
block append meta
if !$.Page.Indexed
meta[name="robots"][content="noindex"]
meta[name="googlebot"][content="noindex"]
block menu
import menu
block main
article.narrow
#{$.Page.Content}

17
view/amber/tags.amber

@ -0,0 +1,17 @@
extends base/master
block menu
import menu
block main
article
h1 All Tags
div.tag-list
each $key, $tags in $.Map
if $tags
div.tl-item
h2 #{$key}
ul
each $tag in $tags
li
a[class=$tag.CSSClass][href=$tag.Path] #{$tag.Name}

26
view/amber/user/login.amber

@ -0,0 +1,26 @@
extends ../base/master
block append css
link[rel="stylesheet"][href="/ui/css/form.css"]
block menu
import menu
block main
article
h1 Login
form[action="/user/login"][method="POST"]
p Use your wiki.aiterp.net account.
p.danger #{$.Error}
input
[placeholder="Username"]
[name="username"]
[type="text"]
[value=$.UserName]
[autofocus] ? $.UserName == ""
input
[placeholder="Password"]
[name="password"]
[type="password"]
[autofocus] ? $.UserName != ""
button[type="submit"] Login

4
view/amber/user/menu.amber

@ -0,0 +1,4 @@
import ../base/mixins
a[href="/"]
h1 User

79
view/renderer.go

@ -2,12 +2,14 @@ package view
import ( import (
"errors" "errors"
"fmt"
"html/template" "html/template"
"io" "io"
"log" "log"
"net/http" "net/http"
"os" "os"
"path"
"github.com/eknkc/amber"
"git.aiterp.net/AiteRP/aitestory/server" "git.aiterp.net/AiteRP/aitestory/server"
"git.aiterp.net/gisle/wrouter/response" "git.aiterp.net/gisle/wrouter/response"
@ -17,52 +19,33 @@ var wd, _ = os.Getwd()
var cache = make(map[string]*template.Template) var cache = make(map[string]*template.Template)
var argsCache = make(map[string][]string) var argsCache = make(map[string][]string)
// Register registers a template and compiles it for rendering. This should be done
// in the beginning since an error will terminate the server
func Register(name string, base string, fragments ...string) {
rootPath := server.Main.Config.Directories.Templates
for i, fragment := range fragments {
fragments[i] = path.Join(rootPath, fragment+".tmpl")
}
args := append([]string{path.Join(rootPath, name+".tmpl"), path.Join(rootPath, base+".tmpl")}, fragments...)
tmpl, err := template.New(name).Funcs(funcMap).ParseFiles(args...)
if err != nil {
log.Fatalf("Failed to register %s: %s", name, err)
}
argsCache[name] = args
cache[name] = tmpl
}
var defaultOptions = amber.Options{PrettyPrint: true}
var defaultDirOptions = amber.DirOptions{Ext: ".amber", Recursive: true}
// Render renders a template with the name it was registered with, and with the // Render renders a template with the name it was registered with, and with the
// view model. The view model is expected to be the correct model from the viewmodel // view model. The view model is expected to be the correct model from the viewmodel
// package // package
func Render(w http.ResponseWriter, name string, status int, viewModel interface{}) { func Render(w http.ResponseWriter, name string, status int, viewModel interface{}) {
var tmpl *template.Template
var err error var err error
if server.Main.Config.Server.Debug { if server.Main.Config.Server.Debug {
tmpl, err = template.New(name).Funcs(funcMap).ParseFiles(argsCache[name]...)
cache, err = amber.CompileDir(server.Main.Config.Directories.Templates, defaultDirOptions, defaultOptions)
if err != nil { if err != nil {
response.Text(w, 500, "Failed to run template "+name+": "+err.Error())
return
log.Println(err)
} }
} else {
var ok bool
}
tmpl, ok = cache[name]
if !ok {
response.Text(w, 500, "Template not found: "+name)
return
}
tmpl, ok := cache[name]
if !ok {
response.Text(w, 500, "Template not found: "+name)
return
} }
w.WriteHeader(status) w.WriteHeader(status)
err = tmpl.ExecuteTemplate(w, "base", viewModel)
err = tmpl.Execute(w, viewModel)
if err != nil { if err != nil {
log.Println("Template error:", err.Error()) log.Println("Template error:", err.Error())
response.Text(w, 500, "Template error: "+err.Error())
} }
} }
@ -77,11 +60,31 @@ func Run(w io.Writer, name string, viewModel interface{}) error {
} }
func init() { func init() {
Register("index", "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")
Register("tags/list", "base/default")
var err error
for key, value := range funcMap {
if _, exists := amber.FuncMap[key]; exists {
log.Printf("Amber already provides a function by the name: %s", key)
}
amber.FuncMap[key] = value
}
cache, err = amber.CompileDir(server.Main.Config.Directories.Templates, defaultDirOptions, defaultOptions)
if err != nil {
if server.Main.Config.Server.Debug {
log.Println(err)
return
}
log.Fatalln(err)
}
if server.Main.Config.Server.Debug {
fmt.Printf("Template directory: %s\n", server.Main.Config.Directories.Templates)
for key := range cache {
fmt.Printf("Template loaded: %s\n", key)
}
}
} }

41
view/templates/base/default.tmpl

@ -1,41 +0,0 @@
{{define "base"}}
<!DOCTYPE html>
<!-- For preview and template creation only -->
<html>
<head>
<title>{{ .ViewTitle }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=2.0, user-scalable=no" />
<meta name="theme-color" content="#222222">
<meta name="robots" content="noindex">
<meta name="googlebot" content="noindex">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="/ui/css/base.css" />
<link rel="stylesheet" href="/ui/css/magic.css" />
<link rel="stylesheet" href="/ui/css/theme.css" />
<link rel="stylesheet" media="screen" href="/ui/fonts/source-sans-pro/source-sans-pro.css" type="text/css"/>
<script type="text/javascript" src="/ui/js/background.js"></script>
{{ block "head" . }}{{end}}
</head>
<body>
<img id="main-background" src="/ui/img/bg.png" />
<div id="content-wrapper">
<main>
{{ block "content" . }}{{end}}
</main>
<nav class="main-menu">
{{ block "menu" . }}{{end}}
</nav>
</div>
</body>
</html>
{{end}}

66
view/templates/index.tmpl

@ -1,66 +0,0 @@
{{ define "content" }}
<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">
<div class="plcm-date">{{.PublishDate | formatDate}}</div>
{{ if .Dated }}
<div class="plcm-date">{{.FictionalDate | 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>
{{ end }}
{{ define "menu" }}
<a href="/"><h1>Aite RP</h1></a>
<ul>
{{ range .Categories }}
<li class="{{ if eq .Key $.ActiveCategory.Key }}selected{{end}}"><a href="/{{.Plural}}/"><div class="mg-icon">{{.Icon}}</div><div class="mg-label">{{.Plural}}</div></a></li>
{{ end }}
</ul>
{{ if $.ActiveTag.ID }}
<ul>
<li class="selected"><a href="/{{$.ActiveTag.Type}}/{{$.ActiveTag.Hook}}"><div class="mg-icon">{{$.ActiveTag.Icon}}</div><div class="mg-label">{{$.ActiveTag.Name}}</div></a></li>
</ul>
{{ end }}
<ul>
<li><a href="/tags/"><div class="mg-icon">T</div><div class="mg-label">Tags</div></a></li>
</ul>
{{ if $.User.LoggedIn }}
<ul>
<li><a href="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li>
</ul>
<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 }}

44
view/templates/page/create.tmpl

@ -1,44 +0,0 @@
{{ define "content" }}
<article>
<h1>{{$.Operation}}</h1>
<form action="/page/create", 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 (e.g. 'Oct 27, 2185')" 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="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>
<!-- 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 }}

37
view/templates/page/delete.tmpl

@ -1,37 +0,0 @@
{{ 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 }}

44
view/templates/page/edit.tmpl

@ -1,44 +0,0 @@
{{ 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 (e.g. 'Oct 27, 2185')" 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="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>
<!-- 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

@ -1,44 +0,0 @@
{{ define "content" }}
<article class="narrow">
{{$.Page.Content}}
</article>
{{ end }}
{{ define "menu" }}
<a href="/"><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="/{{.Type}}/{{.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 }}

48
view/templates/tags/list.tmpl

@ -1,48 +0,0 @@
{{ define "content" }}
<article>
<h1>All Tags</h1>
<div class="tag-list">
{{ range $tagHeader, $tags := .Map }}
<div class="tl-item">
<h2>{{ $tagHeader }}</h2>
<ul>
{{ range $tags }}
<li><a class="ttype-{{.Type | tolower }}" href="/{{.Type}}/{{.Name}}">{{ .Name }}</a></li>
{{ end }}
</ul>
</div>
{{ end }}
</div>
</article>
{{ end }}
{{ define "menu" }}
<a href="/"><h1>Tags</h1></a>
<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="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li>
</ul>
<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 }}

24
view/templates/user/login.tmpl

@ -1,24 +0,0 @@
{{ define "content" }}
<article>
<h1>Login</h1>
<form action="/user/login", method="POST">
<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="Password" name="password" type="password" {{if $.UserName}}autofocus{{end}} />
<button type="submit">Submit</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" />
{{ end }}

9
viewmodel/base.go

@ -16,11 +16,14 @@ type Base struct {
LoggedIn bool LoggedIn bool
SessionID string SessionID string
} }
ViewTitle string
ViewBackground string
ViewPath string
ViewTitle string
Message string
} }
// InitBase initializes the base of the viewmodel // InitBase initializes the base of the viewmodel
func (base *Base) setupBase(user *auth.User, viewTitle string) {
func (base *Base) setupBase(user *auth.User, viewTitle string, viewPath string) {
if user != nil { if user != nil {
base.User.ID = user.FullID() base.User.ID = user.FullID()
base.User.Name = user.ID base.User.Name = user.ID
@ -29,5 +32,7 @@ func (base *Base) setupBase(user *auth.User, viewTitle string) {
base.User.SessionID = user.Session.ID base.User.SessionID = user.Session.ID
} }
base.ViewPath = viewPath
base.ViewTitle = fmt.Sprintf("%s - %s", viewTitle, server.Main.Config.View.Title) base.ViewTitle = fmt.Sprintf("%s - %s", viewTitle, server.Main.Config.View.Title)
} }

16
viewmodel/indexbase.go

@ -0,0 +1,16 @@
package viewmodel
import "git.aiterp.net/AiteRP/aitestory/model"
// IndexBase is used for the index menu.
type IndexBase struct {
Categories []model.PageCategory
ActiveTag model.Tag
ActiveAuthor string
ActiveDate string
ActiveDatePath string
}
func (im *IndexBase) setupMenu() {
im.Categories = model.PageCategories
}

12
viewmodel/pagelist.go → viewmodel/indexlist.go

@ -5,18 +5,18 @@ import (
"git.aiterp.net/gisle/wrouter/auth" "git.aiterp.net/gisle/wrouter/auth"
) )
// PageList is a view model for rendering the front page
type PageList struct {
// IndexList is a view model for rendering the front page
type IndexList struct {
Base Base
IndexBase
Headers []model.Header Headers []model.Header
ActiveCategory model.PageCategory ActiveCategory model.PageCategory
Categories []model.PageCategory
ActiveTag model.Tag
FavoriteTags []model.Tag FavoriteTags []model.Tag
} }
// Setup sets up the page model and the base, and should // Setup sets up the page model and the base, and should
// be run after the details have been filled in. // be run after the details have been filled in.
func (pl *PageList) Setup(user *auth.User) {
pl.setupBase(user, "Index")
func (il *IndexList) Setup(user *auth.User, viewPath string, viewTitle string) {
il.setupMenu()
il.setupBase(user, viewTitle, viewPath)
} }

49
viewmodel/indextags.go

@ -0,0 +1,49 @@
package viewmodel
import (
"fmt"
"git.aiterp.net/AiteRP/aitestory/model"
"git.aiterp.net/gisle/wrouter/auth"
)
// IndexTags is a view model for rendering the tag lists.
type IndexTags struct {
Base
IndexBase
Type string
Tags []model.Tag
TagCategories []string
tagMap map[string][]model.Tag
}
// Map lazy-loads the map of tags, which is only
// used when listing all of them.
func (it IndexTags) Map() map[string][]model.Tag {
// Set up the map
it.tagMap = make(map[string][]model.Tag, len(model.TagTypes))
for _, tagType := range model.TagTypes {
it.tagMap[tagType] = make([]model.Tag, 0, 64)
}
// Organize
for _, tag := range it.Tags {
it.tagMap[tag.Type] = append(it.tagMap[tag.Type], tag)
}
return it.tagMap
}
// Setup sets up the page model and the base, and should
// be run after the details have been filled in.
func (it *IndexTags) Setup(user *auth.User, viewPath string) {
title := it.Type
if it.Type != "" {
title = "All"
}
it.setupMenu()
it.setupBase(user, fmt.Sprintf("%s Tags", title), viewPath)
}

19
viewmodel/message.go

@ -0,0 +1,19 @@
package viewmodel
import "git.aiterp.net/gisle/wrouter/auth"
// Message is a simple viewmodel for the message pages
type Message struct {
Base
Title string
Text string
Danger bool
}
// NewMessage creates a message model
func NewMessage(user *auth.User, title string, danger bool, text string) *Message {
msg := &Message{Title: title, Text: text, Danger: danger}
msg.setupBase(user, title, "")
return msg
}

9
viewmodel/pagebase.go

@ -0,0 +1,9 @@
package viewmodel
import "git.aiterp.net/AiteRP/aitestory/model"
// PageBase describes the basic parts of a page.
type PageBase struct {
Page *model.Page
HideMenu bool
}

26
viewmodel/pagecreate.go

@ -0,0 +1,26 @@
package viewmodel
import (
"git.aiterp.net/AiteRP/aitestory/model"
"git.aiterp.net/gisle/wrouter/auth"
)
// PageCreate is the view model for the page create form.
type PageCreate struct {
Base
IndexBase
Page model.Page
Error string
TagInput string
}
// Setup sets the view model up.
func (pf *PageCreate) Setup(user *auth.User) {
pf.setupMenu()
pf.setupBase(user, "Create", "/page/create")
pf.Categories = model.PageCategories
pf.Page.Defaults()
}

19
viewmodel/pagedelete.go

@ -0,0 +1,19 @@
package viewmodel
import (
"fmt"
"git.aiterp.net/gisle/wrouter/auth"
)
// PageDelete is the view model used to view the page's delete form.
type PageDelete struct {
Base
PageBase
}
// Setup sets up the page view model. It should be calles last
func (pv *PageDelete) Setup(user *auth.User) {
pv.HideMenu = false
pv.setupBase(user, pv.Page.Name, fmt.Sprintf("/page/delete/%s", pv.Page.ID))
}

14
viewmodel/pageform.go → viewmodel/pageedit.go

@ -5,17 +5,19 @@ import (
"git.aiterp.net/gisle/wrouter/auth" "git.aiterp.net/gisle/wrouter/auth"
) )
type PageForm struct {
// PageEdit is the view model for the page edit form.
type PageEdit struct {
Base Base
Page model.Page
PageBase
Categories []model.PageCategory Categories []model.PageCategory
Operation string
Error string Error string
TagInput string TagInput string
} }
func (pf *PageForm) Setup(user *auth.User) {
pf.setupBase(user, "Create")
// Setup sets the view model up.
func (pf *PageEdit) Setup(user *auth.User) {
pf.setupBase(user, "Edit", "/page/edit/"+pf.Page.ID)
pf.Categories = model.PageCategories pf.Categories = model.PageCategories
pf.Page.Defaults()
} }

20
viewmodel/pagesource.go

@ -0,0 +1,20 @@
package viewmodel
import (
"fmt"
"git.aiterp.net/gisle/wrouter/auth"
)
// PageSource is the view model used to view the page's source.
// It's got no extra data on its own.
type PageSource struct {
Base
PageBase
}
// Setup sets up the page view model. It should be calles last
func (pv *PageSource) Setup(user *auth.User) {
pv.HideMenu = false
pv.setupBase(user, pv.Page.Name, fmt.Sprintf("/page/source/%s", pv.Page.ID))
}

11
viewmodel/pageview.go

@ -1,15 +1,20 @@
package viewmodel package viewmodel
import ( import (
"git.aiterp.net/AiteRP/aitestory/model"
"fmt"
"git.aiterp.net/gisle/wrouter/auth" "git.aiterp.net/gisle/wrouter/auth"
) )
// PageView is the view model used to view the page. It's got no extra
// data on its own.
type PageView struct { type PageView struct {
Base Base
Page *model.Page
PageBase
} }
// Setup sets up the page view model. It should be calles last
func (pv *PageView) Setup(user *auth.User) { func (pv *PageView) Setup(user *auth.User) {
pv.setupBase(user, pv.Page.Name)
pv.HideMenu = false
pv.setupBase(user, pv.Page.Name, fmt.Sprintf("/page/%s", pv.Page.ID))
} }

46
viewmodel/taglist.go

@ -1,46 +0,0 @@
package viewmodel
import (
"fmt"
"git.aiterp.net/AiteRP/aitestory/model"
"git.aiterp.net/gisle/wrouter/auth"
)
// TagList is a view model for rendering the tag lists.
type TagList struct {
Base
Type string
Tags []model.Tag
TagCategories []string
tagMap map[string][]model.Tag
}
// Map lazy-loads the map of tags, which is only
// used when listing all of them.
func (tl TagList) Map() map[string][]model.Tag {
// Set up the map
tl.tagMap = make(map[string][]model.Tag, len(model.TagTypes))
for _, tagType := range model.TagTypes {
tl.tagMap[tagType] = make([]model.Tag, 0, 64)
}
// Organize
for _, tag := range tl.Tags {
tl.tagMap[tag.Type] = append(tl.tagMap[tag.Type], tag)
}
return tl.tagMap
}
// Setup sets up the page model and the base, and should
// be run after the details have been filled in.
func (tl *TagList) Setup(user *auth.User) {
title := tl.Type
if tl.Type != "" {
title = "All"
}
tl.setupBase(user, fmt.Sprintf("%s Tags", title))
}

2
viewmodel/userlogin.go

@ -15,5 +15,5 @@ type UserLogin struct {
// Setup sets up the page model and the base, and should // Setup sets up the page model and the base, and should
// be run after the details have been filled in. // be run after the details have been filled in.
func (ul *UserLogin) Setup(user *auth.User) { func (ul *UserLogin) Setup(user *auth.User) {
ul.setupBase(user, "Login")
ul.setupBase(user, "Login", "/user/login")
} }
Loading…
Cancel
Save