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"
"net/http"
"strings"
"time"
"git.aiterp.net/gisle/wrouter/response"
@ -24,10 +25,10 @@ func listIndex(path string, w http.ResponseWriter, req *http.Request, user *auth
return false
}
vm := viewmodel.PageList{}
vm := viewmodel.IndexList{}
vm.Headers, err = model.ListHeaders()
vm.Categories = model.PageCategories
vm.Setup(user)
vm.Setup(user, req.URL.Path, "Index")
if err != nil {
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)
vm := viewmodel.PageList{}
vm := viewmodel.IndexList{}
if tagName != "" {
tag, err := model.FindTag("name", tagName, "")
@ -66,7 +67,7 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
vm.Categories = model.PageCategories
vm.ActiveCategory = category
vm.Setup(user)
vm.Setup(user, path, category.Plural)
if err != nil {
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)
vm := viewmodel.PageList{}
vm := viewmodel.IndexList{}
if tagName == "" {
return false
@ -105,7 +106,7 @@ func listTagged(tagType string) wrouter.FunctionHandlerFunc {
vm.Headers, err = model.ListHeadersByTag("", tag)
vm.Categories = model.PageCategories
vm.Setup(user)
vm.Setup(user, tag.Path(), tag.Name)
if err != nil {
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() {
ListController.Function("/", listIndex)
ListController.Function("/tags/", tagList)
ListController.Function("/author/", listAuthor)
ListController.Function("/month/", listDate)
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 {

44
controllers/pagecontroller.go

@ -25,9 +25,8 @@ func pageCreate(path string, w http.ResponseWriter, req *http.Request, user *aut
return false
}
pc := viewmodel.PageForm{}
pc := viewmodel.PageCreate{}
pc.Setup(user)
pc.Operation = "Create"
// Make sure the user is logged in
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")
}
view.Render(w, "page/create", 200, pc)
view.Render(w, "create", 200, pc)
return true
}
@ -137,6 +136,28 @@ func pageView(path string, w http.ResponseWriter, req *http.Request, user *auth.
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 {
if (req.Method != "GET" && req.Method != "POST") || strings.LastIndex(req.URL.Path, "/") > len(path) {
return false
@ -150,12 +171,12 @@ func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *aut
return false
}
pv := viewmodel.PageView{}
pv := viewmodel.PageDelete{}
pv.Page = page
pv.Setup(user)
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
}
@ -164,7 +185,7 @@ func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *aut
// Catch sneaky shenanigans
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
}
@ -172,12 +193,12 @@ func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *aut
err := page.Delete()
if err != nil {
// 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
}
// 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
}
@ -198,12 +219,10 @@ func pageEdit(path string, w http.ResponseWriter, req *http.Request, user *auth.
return false
}
pf := viewmodel.PageForm{}
pf := viewmodel.PageEdit{}
pf.Page = page
pf.Setup(user)
pf.Operation = "Edit"
pf.Page = *page
if user == nil {
pf.Error = "You are not logged in"
}
@ -285,5 +304,6 @@ func init() {
PageController.Function("/create", pageCreate)
PageController.Function("/edit/", pageEdit)
PageController.Function("/delete/", pageDelete)
PageController.Function("/source/", pageSource)
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
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() {
router := &server.Main.Router
@ -10,10 +16,15 @@ func main() {
auth.Register(&controllers.WikiAthenticator{})
router.Mount("/user", &controllers.UserController)
router.Mount("/page", &controllers.PageController)
router.Mount("/tags", &controllers.TagController)
router.Mount("/", &controllers.ListController)
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()
}

11
model/category.go

@ -9,6 +9,7 @@ type PageCategory struct {
Key string
Plural string
Icon string
Path 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
var PageCategories = []PageCategory{
//{"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"},
{"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

74
model/header.go

@ -3,6 +3,7 @@ package model
import (
"database/sql"
"errors"
"fmt"
"time"
"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
@ -104,6 +110,72 @@ func ListHeadersByCategory(category string) ([]Header, error) {
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
// to not filter by it
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
if len(page.prevTags) == len(page.Tags) {
if len(page.Tags) > 0 && len(page.prevTags) == len(page.Tags) {
change := false
for i, tag := range page.prevTags {
@ -265,6 +265,11 @@ func (page *Page) ParseForm(form url.Values) []error {
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
func (page *Page) generateID() {
page.ID = generate.FriendlyID(16)

21
model/tag.go

@ -18,6 +18,15 @@ var TagTypes = []string{
"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
type Tag struct {
ID string
@ -121,10 +130,14 @@ func (tag *Tag) Validate() error {
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

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 (
"errors"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"path"
"github.com/eknkc/amber"
"git.aiterp.net/AiteRP/aitestory/server"
"git.aiterp.net/gisle/wrouter/response"
@ -17,52 +19,33 @@ var wd, _ = os.Getwd()
var cache = make(map[string]*template.Template)
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
// view model. The view model is expected to be the correct model from the viewmodel
// package
func Render(w http.ResponseWriter, name string, status int, viewModel interface{}) {
var tmpl *template.Template
var err error
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 {
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)
err = tmpl.ExecuteTemplate(w, "base", viewModel)
err = tmpl.Execute(w, viewModel)
if err != nil {
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() {
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
SessionID string
}
ViewTitle string
ViewBackground string
ViewPath string
ViewTitle string
Message string
}
// 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 {
base.User.ID = user.FullID()
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.ViewPath = viewPath
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"
)
// 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
IndexBase
Headers []model.Header
ActiveCategory model.PageCategory
Categories []model.PageCategory
ActiveTag model.Tag
FavoriteTags []model.Tag
}
// Setup sets up the page model and the base, and should
// 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"
)
type PageForm struct {
// PageEdit is the view model for the page edit form.
type PageEdit struct {
Base
Page model.Page
PageBase
Categories []model.PageCategory
Operation string
Error 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.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
import (
"git.aiterp.net/AiteRP/aitestory/model"
"fmt"
"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 {
Base
Page *model.Page
PageBase
}
// Setup sets up the page view model. It should be calles last
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
// be run after the details have been filled in.
func (ul *UserLogin) Setup(user *auth.User) {
ul.setupBase(user, "Login")
ul.setupBase(user, "Login", "/user/login")
}
Loading…
Cancel
Save