Page is done
This commit is contained in:
@@ -58,6 +58,7 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
|
||||
return true
|
||||
}
|
||||
|
||||
vm.ActiveTag = *tag
|
||||
vm.Headers, err = model.ListHeadersByTag(category.Key, tag)
|
||||
} else {
|
||||
vm.Headers, err = model.ListHeadersByCategory(category.Key)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/gisle/wrouter/response"
|
||||
|
||||
"git.aiterp.net/AiteRP/aitestory/model"
|
||||
"git.aiterp.net/AiteRP/aitestory/viewmodel"
|
||||
|
||||
@@ -99,22 +102,188 @@ func pageCreate(path string, w http.ResponseWriter, req *http.Request, user *aut
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Losing input makes the user sad, let's see to it
|
||||
// that it doesn't happen.
|
||||
req.ParseForm()
|
||||
pc.Page.ParseForm(req.Form)
|
||||
pc.TagInput = req.Form.Get("tags")
|
||||
}
|
||||
|
||||
view.Render(w, "create", 200, pc)
|
||||
view.Render(w, "page/create", 200, pc)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func pageView(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
|
||||
if req.Method != "GET" || strings.LastIndex(req.URL.Path, "/") > len(path) {
|
||||
return false
|
||||
}
|
||||
|
||||
page, err := model.FindPage(req.URL.Path[len(path):])
|
||||
if err != nil {
|
||||
response.Text(w, 500, err.Error())
|
||||
return true
|
||||
} else if page == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pv := viewmodel.PageView{}
|
||||
pv.Page = page
|
||||
pv.Setup(user)
|
||||
|
||||
view.Render(w, "page/view", 200, pv)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func pageDelete(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
|
||||
if (req.Method != "GET" && req.Method != "POST") || strings.LastIndex(req.URL.Path, "/") > len(path) {
|
||||
return false
|
||||
}
|
||||
|
||||
page, err := model.FindPage(req.URL.Path[len(path):])
|
||||
if err != nil {
|
||||
response.Text(w, 500, err.Error())
|
||||
return true
|
||||
} else if page == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pv := viewmodel.PageView{}
|
||||
pv.Page = page
|
||||
pv.Setup(user)
|
||||
|
||||
if user == nil || user.FullID() != page.Author {
|
||||
view.Render(w, "message/error-access", http.StatusForbidden, path)
|
||||
return true
|
||||
}
|
||||
|
||||
if req.Method == "POST" {
|
||||
req.ParseForm()
|
||||
|
||||
// Catch sneaky shenanigans
|
||||
if req.Form.Get("aft") != user.Session.ID {
|
||||
view.Render(w, "message/error-forgery", http.StatusForbidden, path)
|
||||
return true
|
||||
}
|
||||
|
||||
// Thy will be done
|
||||
err := page.Delete()
|
||||
if err != nil {
|
||||
// It wasn't done D:
|
||||
view.Render(w, "message/error-internal", http.StatusInternalServerError, err)
|
||||
return true
|
||||
}
|
||||
|
||||
// It has been done
|
||||
view.Render(w, "message/page-deleted", 200, pv)
|
||||
return true
|
||||
}
|
||||
|
||||
view.Render(w, "page/delete", 200, pv)
|
||||
return true
|
||||
}
|
||||
|
||||
func pageEdit(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
|
||||
if (req.Method != "GET" && req.Method != "POST") || strings.LastIndex(req.URL.Path, "/") > len(path) {
|
||||
return false
|
||||
}
|
||||
|
||||
page, err := model.FindPage(req.URL.Path[len(path):])
|
||||
if err != nil {
|
||||
response.Text(w, 500, err.Error())
|
||||
return true
|
||||
} else if page == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
pf := viewmodel.PageForm{}
|
||||
pf.Setup(user)
|
||||
|
||||
pf.Operation = "Edit"
|
||||
pf.Page = *page
|
||||
|
||||
if user == nil {
|
||||
pf.Error = "You are not logged in"
|
||||
}
|
||||
|
||||
if user != nil && user.FullID() != page.Author {
|
||||
pf.Error = "Permission denied"
|
||||
}
|
||||
|
||||
if req.Method == "POST" {
|
||||
req.ParseForm()
|
||||
|
||||
errs := pf.Page.ParseForm(req.Form)
|
||||
if len(errs) > 0 {
|
||||
pf.Error = "Validation failed: " + errs[0].Error()
|
||||
}
|
||||
|
||||
if len(errs) == 0 && pf.Error == "" {
|
||||
pf.TagInput = req.Form.Get("tags")
|
||||
pf.Page.Tags = make([]model.Tag, 0, strings.Count(pf.TagInput, "\n"))
|
||||
tagLines := strings.Split(pf.TagInput, "\n")
|
||||
for _, line := range tagLines {
|
||||
var tagType, tagName string
|
||||
|
||||
// Skip empty lines, and allow some accidental letters
|
||||
if len(line) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse tokens
|
||||
tokens := strings.SplitN(line, ":", 2)
|
||||
if len(tokens) == 2 {
|
||||
tagType = strings.Trim(tokens[0], " \t\r")
|
||||
tagName = strings.Trim(tokens[1], " \t\r")
|
||||
} else {
|
||||
tagType = "*" // Permit untyped tags if it exists.
|
||||
tagName = strings.Trim(tokens[0], " \t\r")
|
||||
}
|
||||
|
||||
// Grab the tag
|
||||
tag, err := model.EnsureTag(tagType, tagName)
|
||||
if err != nil {
|
||||
pf.Error = "Check your tags: " + err.Error()
|
||||
break
|
||||
}
|
||||
|
||||
// Take a copy of it
|
||||
pf.Page.Tags = append(pf.Page.Tags, *tag)
|
||||
}
|
||||
|
||||
if pf.Error == "" {
|
||||
pf.Page.EditDate = time.Now()
|
||||
|
||||
err := pf.Page.Update()
|
||||
if err != nil {
|
||||
pf.Error = "Edit failed: " + err.Error()
|
||||
} else {
|
||||
http.Redirect(w, req, "/page/"+pf.Page.ID, 302)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, tag := range page.Tags {
|
||||
line := fmt.Sprintf("%s: %s\n", tag.Type, tag.Name)
|
||||
|
||||
if page.PrimaryTag().ID == tag.ID {
|
||||
pf.TagInput = line + pf.TagInput
|
||||
} else {
|
||||
pf.TagInput += line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.Render(w, "page/edit", 200, pf)
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
PageController.Function("/create", pageCreate)
|
||||
PageController.Function("/edit/", pageEdit)
|
||||
PageController.Function("/delete/", pageDelete)
|
||||
PageController.Function("/", pageView)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func userLogin(path string, w http.ResponseWriter, req *http.Request, user *auth
|
||||
}
|
||||
|
||||
ul.Setup(user)
|
||||
view.Render(w, "login", 200, ul)
|
||||
view.Render(w, "user/login", 200, ul)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
+7
-7
@@ -21,13 +21,13 @@ func (category *PageCategory) URLRoot() string {
|
||||
// a limited selection of categories. I may move it to a configuration
|
||||
// or the database, but for now I think this list is pretty fixed
|
||||
var PageCategories = []PageCategory{
|
||||
{"OoC", "OoC", "ooc", "OoC content is for announcements, scheduling, general information, or anything that is not in-universe"},
|
||||
{"Info", "Info", "info", "Information gained during and between RP sessions"},
|
||||
{"News", "News", "news", "News stories that might be pertinent to ongoing plots"},
|
||||
{"Item", "Items", "item", "Items relevant to plots, that is more than just a document saved on a character's own omni-tool"},
|
||||
{"Document", "Documents", "document", "Data files, shadow broker dossiers, and other data that is not inside an item"},
|
||||
{"Background", "Background", "background", "Rumors, suspicious persons, or inter-RP occurences that may be noticed"},
|
||||
{"Story", "Stories", "story", "Background stories and inter-RP character intearactions"},
|
||||
{"OoC", "OoC", "O", "OoC content is for announcements, scheduling, general information, or anything that is not in-universe"},
|
||||
{"Info", "Info", "i", "Information gained during and between RP sessions"},
|
||||
{"News", "News", "N", "News stories that might be pertinent to ongoing plots"},
|
||||
{"Item", "Items", "I", "Items relevant to plots, that is more than just a document saved on a character's own omni-tool"},
|
||||
{"Document", "Document", "D", "Data files, shadow broker dossiers, and other data that is not inside an item"},
|
||||
{"Background", "Background", "B", "Rumors, suspicious persons, or inter-RP occurences that may be noticed"},
|
||||
{"Story", "Stories", "S", "Background stories and inter-RP character intearactions"},
|
||||
}
|
||||
var pageCategories []string
|
||||
|
||||
|
||||
+20
-5
@@ -23,6 +23,17 @@ type Header struct {
|
||||
PrimaryTag *Tag `json:"primaryTag"`
|
||||
}
|
||||
|
||||
// CategoryInfo gets information about the category
|
||||
func (header *Header) CategoryInfo() PageCategory {
|
||||
for _, category := range PageCategories {
|
||||
if category.Key == header.Category {
|
||||
return category
|
||||
}
|
||||
}
|
||||
|
||||
return PageCategory{"Unknown", "Unknown", "?", ""}
|
||||
}
|
||||
|
||||
// ListHeaders grabs all the general pages from
|
||||
// the database to list them
|
||||
func ListHeaders() ([]Header, error) {
|
||||
@@ -31,7 +42,8 @@ func ListHeaders() ([]Header, error) {
|
||||
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;
|
||||
WHERE page.specific=false AND page.published=true AND page.unlisted=false
|
||||
ORDER BY page.fictional_date DESC
|
||||
`
|
||||
|
||||
db := server.Main.DB
|
||||
@@ -63,7 +75,8 @@ func ListHeadersByCategory(category string) ([]Header, error) {
|
||||
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.category = ?;
|
||||
WHERE page.specific=false AND page.published=true AND page.unlisted=false AND page.category = ?
|
||||
ORDER BY page.fictional_date DESC
|
||||
`
|
||||
|
||||
db := server.Main.DB
|
||||
@@ -97,7 +110,8 @@ func ListHeadersByTag(category string, tag *Tag) ([]Header, error) {
|
||||
RIGHT JOIN page ON page.id = page_tag.page_id
|
||||
LEFT JOIN (page_tag AS pt2) ON (page.id = pt2.page_id AND pt2.primary = true)
|
||||
LEFT JOIN (tag AS tag) ON (tag.id = pt2.tag_id)
|
||||
WHERE page_tag.tag_id=?
|
||||
WHERE page_tag.tag_id=? AND page.unlisted=false AND page.published=true
|
||||
ORDER BY page.fictional_date DESC
|
||||
`
|
||||
const query2 = `
|
||||
SELECT page.id,page.name,page.author,page.category,page.fictional_date,page.publish_date,page.edit_date,page.dated,tag.id,tag.type,tag.name
|
||||
@@ -105,7 +119,8 @@ func ListHeadersByTag(category string, tag *Tag) ([]Header, error) {
|
||||
RIGHT JOIN page ON page.id = page_tag.page_id
|
||||
LEFT JOIN (page_tag AS pt2) ON (page.id = pt2.page_id AND pt2.primary = true)
|
||||
LEFT JOIN (tag AS tag) ON (tag.id = pt2.tag_id)
|
||||
WHERE page_tag.tag_id=? AND page.category=?
|
||||
WHERE page_tag.tag_id=? AND page.category=? AND page.unlisted=false AND page.published=true
|
||||
ORDER BY page.fictional_date DESC
|
||||
`
|
||||
|
||||
if tag == nil {
|
||||
@@ -190,7 +205,7 @@ func parseHeader(header *Header, rows *sql.Rows) error {
|
||||
|
||||
rows.Scan(&header.ID, &header.Name, &header.Author, &header.Category, &fictionalDate, &publishDate, &editDate, &header.Dated, &tagID, &tagType, &tagName)
|
||||
if tagID != "" {
|
||||
header.PrimaryTag = &Tag{tagID, tagName, tagType}
|
||||
header.PrimaryTag = &Tag{tagID, tagType, tagName}
|
||||
}
|
||||
|
||||
header.FictionalDate, err = time.Parse("2006-01-02 15:04:05", fictionalDate)
|
||||
|
||||
+30
-10
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -46,6 +47,7 @@ type Page struct {
|
||||
|
||||
prevTags []Tag
|
||||
cachedOutput string
|
||||
primaryTag *Tag
|
||||
}
|
||||
|
||||
// Defaults fills in the default details for a page, suited for populating a form
|
||||
@@ -99,6 +101,10 @@ func (page *Page) Insert() error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(page.Tags) > 0 {
|
||||
page.primaryTag = &page.Tags[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -157,6 +163,9 @@ func (page *Page) Update() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(page.Tags) > 0 {
|
||||
page.primaryTag = &page.Tags[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -184,29 +193,33 @@ func (page *Page) Delete() error {
|
||||
}
|
||||
|
||||
// Content parses the content of the page
|
||||
func (page *Page) Content() (string, error) {
|
||||
if page.cachedOutput != "" {
|
||||
return page.cachedOutput, nil
|
||||
}
|
||||
|
||||
func (page *Page) Content() (template.HTML, error) {
|
||||
if page.Type == "Markdown" {
|
||||
// TODO: Convert [[Ehanis Tioran]] to [Ehanis Tioran](https://wiki.aiterp.net/index.php?title=Ehanis%20Tioran)
|
||||
|
||||
unsafe := blackfriday.MarkdownCommon([]byte(page.Source))
|
||||
page.cachedOutput = string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
|
||||
output := string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
|
||||
|
||||
return page.cachedOutput, nil
|
||||
return template.HTML(output), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Page type '%s' is not supported", page.Type)
|
||||
}
|
||||
|
||||
// PrimaryTag gets the page's primary tag
|
||||
func (page *Page) PrimaryTag() Tag {
|
||||
if page.primaryTag == nil {
|
||||
return Tag{}
|
||||
}
|
||||
|
||||
return *page.primaryTag
|
||||
}
|
||||
|
||||
// ParseForm validates the values in a form and sets the page's values whenever possible regardless
|
||||
// so that it can be pushed to the viewmodel to allow the user to correct their mistakes without fear
|
||||
// of losing their hard work
|
||||
func (page *Page) ParseForm(form url.Values) []error {
|
||||
errors := make([]error, 0, 4)
|
||||
page.cachedOutput = ""
|
||||
|
||||
err := formparser.String(form.Get("name"), &page.Name, 2, 192)
|
||||
if err != nil {
|
||||
@@ -266,10 +279,11 @@ func FindPage(id string) (*Page, error) {
|
||||
WHERE id=?
|
||||
`
|
||||
const selectPageTags = `
|
||||
SELECT tag.id,tag.type,tag.name
|
||||
SELECT tag.id,tag.type,tag.name,page_tag.primary
|
||||
FROM page_tag
|
||||
RIGHT JOIN tag ON (tag.id = page_tag.tag_id)
|
||||
WHERE page_tag.page_id = ?
|
||||
ORDER BY tag.type DESC, tag.name
|
||||
`
|
||||
|
||||
db := server.Main.DB
|
||||
@@ -297,9 +311,15 @@ func FindPage(id string) (*Page, error) {
|
||||
|
||||
page.Tags = make([]Tag, 0, 64)
|
||||
for rows.Next() {
|
||||
primary := false
|
||||
|
||||
tag := Tag{}
|
||||
rows.Scan(&tag.ID, &tag.Type, &tag.Name)
|
||||
rows.Scan(&tag.ID, &tag.Type, &tag.Name, &primary)
|
||||
page.Tags = append(page.Tags, tag)
|
||||
|
||||
if primary {
|
||||
page.primaryTag = &tag
|
||||
}
|
||||
}
|
||||
|
||||
return page, nil
|
||||
|
||||
+1
-1
@@ -259,7 +259,7 @@ func TestPage(t *testing.T) {
|
||||
t.Errorf("page.Content: %s", err)
|
||||
}
|
||||
|
||||
assertEquals(t, "page.Content()", content, "<h1>Returning Va’ynna’s Omni-Tool</h1>\n\n<p>Additional Content is additional</p>\n")
|
||||
assertEquals(t, "page.Content()", string(content), "<h1>Returning Va’ynna’s Omni-Tool</h1>\n\n<p>Additional Content is additional</p>\n")
|
||||
})
|
||||
|
||||
t.Run("WikiURL", func(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/AiteRP/aitestory/server"
|
||||
@@ -24,6 +25,17 @@ type Tag struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// Icon is the API used to get the icon for the tag.
|
||||
func (tag Tag) Icon() string {
|
||||
return tag.Type[0:1]
|
||||
}
|
||||
|
||||
// CSSCLass gets the CSS class that colors the tag on the
|
||||
// page list
|
||||
func (tag Tag) CSSCLass() string {
|
||||
return fmt.Sprintf("ttype-%s", strings.ToLower(tag.Type))
|
||||
}
|
||||
|
||||
// Insert adds the tag to the database, giving it a new unique ID
|
||||
func (tag *Tag) Insert() error {
|
||||
db := server.Main.DB
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func formatDate(date time.Time) string {
|
||||
return date.Format("Jan _2, 2006")
|
||||
}
|
||||
|
||||
func formatDateLong(date time.Time) string {
|
||||
return date.Format("Jan _2, 2006 15:04 MST")
|
||||
}
|
||||
|
||||
func formatUserID(userid string) string {
|
||||
split := strings.SplitN(userid, ":", 2)
|
||||
return split[len(split)-1]
|
||||
}
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"formatDate": formatDate,
|
||||
"formatDateLong": formatDateLong,
|
||||
"formatUserID": formatUserID,
|
||||
|
||||
"tolower": strings.ToLower,
|
||||
}
|
||||
+7
-4
@@ -27,7 +27,7 @@ func Register(name string, base string, fragments ...string) {
|
||||
}
|
||||
|
||||
args := append([]string{path.Join(rootPath, name+".tmpl"), path.Join(rootPath, base+".tmpl")}, fragments...)
|
||||
tmpl, err := template.New(name).ParseFiles(args...)
|
||||
tmpl, err := template.New(name).Funcs(funcMap).ParseFiles(args...)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to register %s: %s", name, err)
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func Render(w http.ResponseWriter, name string, status int, viewModel interface{
|
||||
var err error
|
||||
|
||||
if server.Main.Config.Server.Debug {
|
||||
tmpl, err = template.New(name).ParseFiles(argsCache[name]...)
|
||||
tmpl, err = template.New(name).Funcs(funcMap).ParseFiles(argsCache[name]...)
|
||||
if err != nil {
|
||||
response.Text(w, 500, "Failed to run template "+name+": "+err.Error())
|
||||
return
|
||||
@@ -78,6 +78,9 @@ func Run(w io.Writer, name string, viewModel interface{}) error {
|
||||
|
||||
func init() {
|
||||
Register("index", "base/default")
|
||||
Register("login", "base/default")
|
||||
Register("create", "base/default")
|
||||
Register("user/login", "base/default")
|
||||
Register("page/create", "base/default")
|
||||
Register("page/edit", "base/default")
|
||||
Register("page/view", "base/default")
|
||||
Register("page/delete", "base/default")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
{{ 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">
|
||||
{{ if .Dated }}
|
||||
<div class="plcm-date">{{.FictionalDate | formatDate}}</div>
|
||||
{{ else }}
|
||||
<div class="plcm-date">{{.PublishDate | formatDate}}</div>
|
||||
{{ end }}
|
||||
{{ if .PrimaryTag }}
|
||||
<div class="plcm-tag {{.PrimaryTag.CSSCLass}}">{{.PrimaryTag.Name}}</div>
|
||||
{{ end }}
|
||||
<div class="plcm-author">{{.Author | formatUserID}}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="spacer"><td></td></tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
</article>
|
||||
{{ end }}
|
||||
|
||||
@@ -9,10 +31,16 @@
|
||||
|
||||
<ul>
|
||||
{{ range .Categories }}
|
||||
<li class="{{ if eq .Key $.ActiveCategory.Key }}selected{{end}}"><a href="/{{.URLRoot}}/{{$.ActiveTag.Hook}}"><div class="mg-icon">Π</div><div class="mg-label">{{.Plural}}</div></a></li>
|
||||
<li class="{{ if eq .Key $.ActiveCategory.Key }}selected{{end}}"><a href="/{{.URLRoot}}/"><div class="mg-icon">{{.Icon}}</div><div class="mg-label">{{.Plural}}</div></a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
{{ if $.ActiveTag.ID }}
|
||||
<ul>
|
||||
<li class="selected"><a href="/{{$.ActiveTag.Hook}}"><div class="mg-icon">{{$.ActiveTag.Icon}}</div><div class="mg-label">{{$.ActiveTag.Name}}</div></a></li>
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
{{ if $.User.LoggedIn }}
|
||||
<ul>
|
||||
<li><a href="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{{ define "content" }}
|
||||
<article>
|
||||
<h1>Create</h1>
|
||||
<h1>{{$.Operation}}</h1>
|
||||
<form action="/page/create", method="POST">
|
||||
<p class="red">{{$.Error}}</p>
|
||||
<input class="big" placeholder="Page Name" name="name" type="text" value="{{$.Page.Name}}" autofocus />
|
||||
<p class="danger">{{$.Error}}</p>
|
||||
<input class="big" placeholder="Page Name" name="name" type="text" value="{{$.Page.Name}}" />
|
||||
<textarea class="tall" placeholder="Content" name="source">{{$.Page.Source}}</textarea>
|
||||
<input placeholder="IC Date" name="fictionalDate" type="text" value="{{if $.Page.FictionalDate.IsZero}}{{else}}{{$.Page.FictionalDate}}{{end}}" autofocus />
|
||||
<input placeholder="IC Date" name="fictionalDate" type="text" value="{{if $.Page.FictionalDate.IsZero}}{{else}}{{$.Page.FictionalDate}}{{end}}" />
|
||||
<div class="group">
|
||||
{{ range $.Categories }}
|
||||
<div class="radio-wrapper">
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<textarea name="tags" placeholder="Tags, e.g. 'Location: Miner's Respite'. One per line, topmost is primary tag"></textarea>
|
||||
<textarea name="tags" placeholder="Tags, e.g. 'Location: Miner's Respite'. One per line, topmost is primary tag">{{$.TagInput}}</textarea>
|
||||
<div class="group">
|
||||
<div class="radio-wrapper">
|
||||
<input type="checkbox" name="dated" value="true" {{if $.Page.Dated}}checked{{end}}><b> Dated</b>: The IC date is shown on the page list. It will still be used for sorting if this option is disabled.</input>
|
||||
@@ -30,7 +30,7 @@
|
||||
<input type="hidden" name="type" value="Markdown" />
|
||||
<input type="hidden" name="published" value="True" />
|
||||
|
||||
<button type="submit">Submit</button>
|
||||
<button type="submit">{{$.Operation}}</button>
|
||||
|
||||
</form>
|
||||
</article>
|
||||
@@ -0,0 +1,37 @@
|
||||
{{ define "content" }}
|
||||
<article class="narrow">
|
||||
<h1 class="danger">Delete Page</h1>
|
||||
<form action="/page/delete/{{$.Page.ID}}", method="POST">
|
||||
<p class="danger">
|
||||
This is an irreversible action, so make sure that this is the correct page!
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>ID:</b> {{$.Page.ID}}</li>
|
||||
<li><b>Name:</b> {{$.Page.Name}}</li>
|
||||
<li><b>Category:</b> {{$.Page.Category}}</li>
|
||||
<li><b>Published:</b> {{$.Page.PublishDate | formatDateLong}}</li>
|
||||
</ul>
|
||||
<input type="hidden" name="aft" value="{{$.User.SessionID}}" />
|
||||
|
||||
<button type="submit">Delete Page</button>
|
||||
</article>
|
||||
{{ end }}
|
||||
|
||||
{{ define "menu" }}
|
||||
<a href="/"><h1>Page</h1></a>
|
||||
|
||||
{{ if eq $.User.ID $.Page.Author}}
|
||||
<ul>
|
||||
<li><a href="/page/edit/{{$.Page.ID}}"><div class="mg-icon">E</div><div class="mg-label">Edit</div></a></li>
|
||||
<li class="selected"><a href="/page/delete/{{$.Page.ID}}"><div class="mg-icon">X</div><div class="mg-label">Delete</div></a></li>
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
<ul>
|
||||
<li><a href="/page/{{$.Page.ID}}"><div class="mg-icon"><</div><div class="mg-label">Back</div></a></li>
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
{{ define "head" }}
|
||||
<link rel="stylesheet" href="/ui/css/form.css" />
|
||||
{{ end }}
|
||||
@@ -0,0 +1,50 @@
|
||||
{{ define "content" }}
|
||||
<article>
|
||||
<h1>{{$.Operation}}</h1>
|
||||
<form action="/page/edit/{{$.Page.ID}}", method="POST">
|
||||
<p class="danger">{{$.Error}}</p>
|
||||
<input class="big" placeholder="Page Name" name="name" type="text" value="{{$.Page.Name}}" />
|
||||
<textarea class="tall" placeholder="Content" name="source">{{$.Page.Source}}</textarea>
|
||||
<input placeholder="IC Date" name="fictionalDate" type="text" value="{{if $.Page.FictionalDate.IsZero}}{{else}}{{$.Page.FictionalDate}}{{end}}" />
|
||||
<div class="group">
|
||||
{{ range $.Categories }}
|
||||
<div class="radio-wrapper">
|
||||
<input name="category" type="radio" name="category" value="{{.Key}}" {{if eq $.Page.Category .Key}}checked{{end}}><b> {{.Key}}</b>: {{.Info}}</input>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<textarea name="tags" placeholder="Tags, e.g. 'Location: Miner's Respite'. One per line, topmost is primary tag">{{$.TagInput}}</textarea>
|
||||
<div class="group">
|
||||
<div class="radio-wrapper">
|
||||
<input type="checkbox" name="dated" value="true" {{if $.Page.Dated}}checked{{end}}><b> Dated</b>: The IC date is shown on the page list. It will still be used for sorting if this option is disabled.</input>
|
||||
</div>
|
||||
<div class="radio-wrapper">
|
||||
<input type="checkbox" name="unlisted" value="true" {{if $.Page.Unlisted}}checked{{end}}><b> Unlisted</b>: This page will not show up on page lists, but anyone with a link can view it.</input>
|
||||
</div>
|
||||
<div class="radio-wrapper">
|
||||
<input type="checkbox" name="specific" value="true" {{if $.Page.Specific}}checked{{end}}><b> Specific</b>: This page will only show up on page lists when one of its tags is searched for.</input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Future option -->
|
||||
<input type="hidden" name="type" value="Markdown" />
|
||||
<input type="hidden" name="published" value="True" />
|
||||
|
||||
<button type="submit">{{$.Operation}}</button>
|
||||
|
||||
</form>
|
||||
</article>
|
||||
{{ end }}
|
||||
|
||||
{{ define "menu" }}
|
||||
<a href="/"><h1>Aite RP</h1></a>
|
||||
|
||||
<ul>
|
||||
<li><a href="/"><div class="mg-icon"><</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 }}
|
||||
@@ -0,0 +1,44 @@
|
||||
{{ define "content" }}
|
||||
<article class="narrow">
|
||||
{{$.Page.Content}}
|
||||
</article>
|
||||
{{ end }}
|
||||
|
||||
{{ define "menu" }}
|
||||
<a href="/page/{{$.Page.ID}}"><h1>Page</h1></a>
|
||||
{{ if $.Page.Dated}}
|
||||
<div class="page-property">{{$.Page.FictionalDate | formatDate}}</div>
|
||||
{{ else }}
|
||||
<div class="page-property">{{$.Page.PublishDate | formatDate}}</div>
|
||||
{{ end }}
|
||||
|
||||
<ul>
|
||||
{{ range $.Page.Tags }}
|
||||
<li><a href="/{{.Hook}}"><div class="mg-icon">{{.Icon}}</div><div class="mg-label {{.CSSCLass}}">{{.Name}}</div></a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
{{ if eq $.User.ID $.Page.Author}}
|
||||
<ul>
|
||||
<li><a href="/page/edit/{{$.Page.ID}}"><div class="mg-icon">E</div><div class="mg-label">Edit</div></a></li>
|
||||
<li><a href="/page/delete/{{$.Page.ID}}"><div class="mg-icon">X</div><div class="mg-label">Delete</div></a></li>
|
||||
</ul>
|
||||
{{ end }}
|
||||
|
||||
<ul>
|
||||
<li><a href="/"><div class="mg-icon"><</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 }}
|
||||
@@ -2,7 +2,8 @@
|
||||
<article>
|
||||
<h1>Login</h1>
|
||||
<form action="/user/login", method="POST">
|
||||
<p class="red">{{$.Error}}</p>
|
||||
<p>Use your wiki.aiterp.net account.</p>
|
||||
<p class="danger">{{$.Error}}</p>
|
||||
<input placeholder="Username" name="username" type="text" value="{{$.UserName}}" {{if ne $.UserName ""}}autofocus{{end}} />
|
||||
<input placeholder="Password" name="password" type="password" {{if $.UserName}}autofocus{{end}} />
|
||||
<button type="submit">Submit</button>
|
||||
+6
-4
@@ -11,10 +11,11 @@ import (
|
||||
// Base is the basic information used to render the page
|
||||
type Base struct {
|
||||
User struct {
|
||||
ID string
|
||||
Name string
|
||||
Role string
|
||||
LoggedIn bool
|
||||
ID string
|
||||
Name string
|
||||
Role string
|
||||
LoggedIn bool
|
||||
SessionID string
|
||||
}
|
||||
ViewTitle string
|
||||
}
|
||||
@@ -28,6 +29,7 @@ func (base *Base) setupBase(user *auth.User, viewTitle string) {
|
||||
base.User.Name = user.ID
|
||||
base.User.Role = user.Data["role"]
|
||||
base.User.LoggedIn = true
|
||||
base.User.SessionID = user.Session.ID
|
||||
}
|
||||
|
||||
base.ViewTitle = fmt.Sprintf("%s - %s", viewTitle, server.Main.Config.View.Title)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
type PageView struct {
|
||||
Base
|
||||
Page model.Page
|
||||
Page *model.Page
|
||||
}
|
||||
|
||||
func (pv *PageView) Setup(user *auth.User) {
|
||||
|
||||
Reference in New Issue
Block a user