Changed tags to allow same-name taggs with different categories, added tag list
This commit is contained in:
@@ -52,7 +52,7 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
|
||||
vm := viewmodel.PageList{}
|
||||
|
||||
if tagName != "" {
|
||||
tag, err := model.FindTag("name", tagName)
|
||||
tag, err := model.FindTag("name", tagName, "")
|
||||
if err != nil {
|
||||
response.Text(w, 404, err.Error())
|
||||
return true
|
||||
@@ -79,11 +79,53 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func listTagged(tagType string) wrouter.FunctionHandlerFunc {
|
||||
return func(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
|
||||
}
|
||||
|
||||
tagName := strings.Replace(req.URL.Path[len(path):], "_", " ", -1)
|
||||
|
||||
vm := viewmodel.PageList{}
|
||||
|
||||
if tagName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
tag, err := model.FindTag("name", tagName, tagType)
|
||||
if err != nil {
|
||||
response.Text(w, 404, err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
vm.ActiveTag = *tag
|
||||
vm.Headers, err = model.ListHeadersByTag("", tag)
|
||||
|
||||
vm.Categories = model.PageCategories
|
||||
vm.Setup(user)
|
||||
|
||||
if err != nil {
|
||||
response.Text(w, 500, err.Error())
|
||||
return true
|
||||
}
|
||||
|
||||
view.Render(w, "index", 200, vm)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
ListController.Function("/", listIndex)
|
||||
|
||||
ListController.Function("/", listFiltered(model.PageCategory{Key: ""}))
|
||||
for _, category := range model.PageCategories {
|
||||
ListController.Function(fmt.Sprintf("/%s/", strings.ToLower(category.Plural)), listFiltered(category))
|
||||
}
|
||||
|
||||
for _, tagType := range model.TagTypes {
|
||||
ListController.Function(fmt.Sprintf("/%s/", strings.ToLower(tagType)), listTagged(tagType))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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)
|
||||
|
||||
+38
-7
@@ -129,8 +129,9 @@ func (tag Tag) Hook() string {
|
||||
return strings.Replace(tag.Name, " ", "_", -1)
|
||||
}
|
||||
|
||||
// FindTag finds a tag by ID
|
||||
func FindTag(key string, id string) (*Tag, error) {
|
||||
// FindTag finds a tag by ID. Leave tagtype blank to ignore it. Both key and
|
||||
// id are checked against their respective lists to prevent SQL injection attacks.
|
||||
func FindTag(key string, id string, tagType string) (*Tag, error) {
|
||||
db := server.Main.DB
|
||||
|
||||
// Make damn sure that – should this take user data as key – it
|
||||
@@ -139,7 +140,24 @@ func FindTag(key string, id string) (*Tag, error) {
|
||||
return nil, errors.New("invalid key")
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT id,type,name FROM `tag` WHERE "+key+"=? AND disabled=false", id)
|
||||
query := "SELECT id,type,name FROM `tag` WHERE " + key + "=? AND disabled=false"
|
||||
if tagType != "" {
|
||||
// Prevent more shenanigans
|
||||
found := false
|
||||
for _, existing := range TagTypes {
|
||||
if existing == tagType {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, errors.New("invalid tag type")
|
||||
}
|
||||
|
||||
query += fmt.Sprintf(` AND type="%s"`, tagType)
|
||||
}
|
||||
|
||||
rows, err := db.Query(query, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -156,7 +174,7 @@ func FindTag(key string, id string) (*Tag, error) {
|
||||
// EnsureTag finds or creates a tag.
|
||||
func EnsureTag(tagType, tagName string) (*Tag, error) {
|
||||
// Try to find it first
|
||||
tag, err := FindTag("name", tagName)
|
||||
tag, err := FindTag("name", tagName, tagType)
|
||||
if err != nil && err.Error() != "not found" { // You saw nothing
|
||||
return nil, err
|
||||
}
|
||||
@@ -179,7 +197,15 @@ func EnsureTag(tagType, tagName string) (*Tag, error) {
|
||||
func ListTags() ([]Tag, error) {
|
||||
db := server.Main.DB
|
||||
|
||||
rows, err := db.Query("SELECT id,type,name FROM `tag` WHERE disabled=false")
|
||||
query := `
|
||||
SELECT id,type,name,count(pt.page_id) FROM tag
|
||||
LEFT JOIN page_tag AS pt ON (pt.tag_id = tag.id)
|
||||
WHERE disabled=false
|
||||
GROUP BY pt.tag_id
|
||||
ORDER BY type,name
|
||||
`
|
||||
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -187,9 +213,14 @@ func ListTags() ([]Tag, error) {
|
||||
|
||||
results := make([]Tag, 0, 64)
|
||||
for rows.Next() {
|
||||
count := 0
|
||||
tag := Tag{}
|
||||
rows.Scan(&tag.ID, &tag.Type, &tag.Name)
|
||||
results = append(results, tag)
|
||||
rows.Scan(&tag.ID, &tag.Type, &tag.Name, &count)
|
||||
|
||||
// Let's pretend unused tags don't exist. >_>
|
||||
if count > 0 {
|
||||
results = append(results, tag)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -83,4 +83,5 @@ func init() {
|
||||
Register("page/edit", "base/default")
|
||||
Register("page/view", "base/default")
|
||||
Register("page/delete", "base/default")
|
||||
Register("tags/list", "base/default")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<link rel="stylesheet" href="/ui/css/magic.css" />
|
||||
<link rel="stylesheet" href="/ui/css/theme.css" />
|
||||
|
||||
<link rel="stylesheet" media="screen" href="/ui/fonts/SeanSans.css" type="text/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>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<ul>
|
||||
{{ range .Categories }}
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
</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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{ end }}
|
||||
|
||||
{{ define "menu" }}
|
||||
<a href="/page/{{$.Page.ID}}"><h1>Page</h1></a>
|
||||
<a href="/"><h1>Page</h1></a>
|
||||
{{ if $.Page.Dated}}
|
||||
<div class="page-property">{{$.Page.FictionalDate | formatDate}}</div>
|
||||
{{ else }}
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<ul>
|
||||
{{ range $.Page.Tags }}
|
||||
<li><a href="/{{.Hook}}"><div class="mg-icon">{{.Icon}}</div><div class="mg-label {{.CSSCLass}}">{{.Name}}</div></a></li>
|
||||
<li><a href="/{{.Type}}/{{.Hook}}"><div class="mg-icon">{{.Icon}}</div><div class="mg-label {{.CSSCLass}}">{{.Name}}</div></a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
{{ 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"><</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 }}
|
||||
@@ -0,0 +1,46 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user