added sections overview

This commit is contained in:
ston1th 2019-01-22 21:45:32 +01:00
commit 799ff0fb2f
16 changed files with 181 additions and 66 deletions

View file

@ -22,6 +22,23 @@ func (p Pages) Len() int { return len(p) }
func (p Pages) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p Pages) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Pages) Less(i, j int) bool { return p[i].StoreTitle < p[j].StoreTitle } func (p Pages) Less(i, j int) bool { return p[i].StoreTitle < p[j].StoreTitle }
type UpdatedPages []Page
func (p UpdatedPages) Len() int { return len(p) }
func (p UpdatedPages) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p UpdatedPages) Less(i, j int) bool { return p[i].Updated > p[j].Updated }
type Section struct {
Section string
Pages UpdatedPages
}
type Sections []Section
func (s Sections) Len() int { return len(s) }
func (s Sections) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s Sections) Less(i, j int) bool { return s[i].Section < s[j].Section }
type User struct { type User struct {
Username string Username string
Password string Password string

View file

@ -8,12 +8,12 @@ import (
) )
var reservedUsers = []string{ var reservedUsers = []string{
"all",
"blacklist", "blacklist",
"login", "login",
"logout", "logout",
"new", "new",
"search", "search",
"sections",
"user", "user",
"view", "view",
"wiki", "wiki",

View file

@ -8,6 +8,7 @@ import (
"git.giftfish.de/ston1th/gowiki/pkg/render" "git.giftfish.de/ston1th/gowiki/pkg/render"
"git.giftfish.de/ston1th/gowiki/pkg/store" "git.giftfish.de/ston1th/gowiki/pkg/store"
"sort" "sort"
"strings"
) )
const pagePrefix = "page/" const pagePrefix = "page/"
@ -21,9 +22,42 @@ var (
errDeleteIndexPage = errors.New("db: index page can not be deleted") errDeleteIndexPage = errors.New("db: index page can not be deleted")
) )
func (db *DB) GetAllPages(username string) (pages core.Pages) { func (db *DB) GetAllSections(username string) (sections core.Sections) {
m := make(map[string]core.UpdatedPages)
db.store.ForEach(func(k string, _ []byte) error { db.store.ForEach(func(k string, _ []byte) error {
if trim, ok := hasTrimPrefix(k, pagePrefix); ok { if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
if trim == core.IndexURI {
return nil
}
p, err := db.getPage(trim, username)
if err == nil {
s := trim[:strings.Index(trim, "/")]
if _, ok := m[s]; !ok {
m[s] = core.UpdatedPages{}
}
m[s] = append(m[s], *p)
}
}
return nil
})
for s, p := range m {
sort.Sort(p)
l := len(p)
if l > 5 {
l = 5
}
sections = append(sections, core.Section{s, p[:l]})
}
sort.Sort(sections)
return
}
func (db *DB) GetAllSectionPages(section, username string) (pages core.Pages, err error) {
db.store.ForEach(func(k string, _ []byte) error {
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
if !strings.HasPrefix(trim, section+"/") {
return nil
}
if trim == core.IndexURI { if trim == core.IndexURI {
return nil return nil
} }
@ -34,6 +68,10 @@ func (db *DB) GetAllPages(username string) (pages core.Pages) {
} }
return nil return nil
}) })
if len(pages) == 0 {
err = errPageNotFound
return
}
sort.Sort(pages) sort.Sort(pages)
return return
} }

View file

@ -205,14 +205,31 @@ func searchHandler(ctx *Context) {
} }
} }
func allHandler(ctx *Context) { func sectionsHandler(ctx *Context) {
ctx.Template("allHandler") ctx.Template("sectionsHandler")
ctx.Data = webData{ ctx.Data = webData{
Title: "All Pages", Title: "All Sections",
BodyTitle: "All Pages", BodyTitle: "All Sections",
Admin: ctx.Admin(), Admin: ctx.Admin(),
} }
ctx.Data.Data = ctx.Srv.DB.GetAllPages(ctx.User()) ctx.Data.Data = ctx.Srv.DB.GetAllSections(ctx.User())
ctx.Exec()
}
func sectionHandler(ctx *Context) {
ctx.Template("sectionHandler")
section := ctx.Var("section")
ctx.Data = webData{
Title: section,
BodyTitle: section,
Admin: ctx.Admin(),
}
var err error
ctx.Data.Data, err = ctx.Srv.DB.GetAllSectionPages(section, ctx.User())
if err != nil {
ctx.NotFound()
return
}
ctx.Exec() ctx.Exec()
} }

View file

@ -24,6 +24,11 @@ var routes = []route{
staticHandler, staticHandler,
[]string{"GET"}, []string{"GET"},
}, },
{
"/sections",
sectionsHandler,
[]string{"GET"},
},
{ {
"/search", "/search",
searchHandler, searchHandler,
@ -35,11 +40,6 @@ var routes = []route{
pageNewHandler), pageNewHandler),
[]string{"GET", "POST"}, []string{"GET", "POST"},
}, },
{
"/all",
allHandler,
[]string{"GET"},
},
{ {
"/login", "/login",
loginHandler, loginHandler,
@ -109,6 +109,11 @@ var routes = []route{
pageHandler), pageHandler),
[]string{"GET"}, []string{"GET"},
}, },
{
"/{section:[a-zA-Z0-9]+}",
sectionHandler,
[]string{"GET"},
},
{ {
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}", "/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}",
pageHandler, pageHandler,

View file

@ -12,37 +12,6 @@ import (
) )
const ( const (
all = `{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
{{if .Data}}
<div class="row mt-5">
{{$idx0 := index .Data 0}}
{{$owner := $idx0.Owner}}
<strong>{{$owner}}</strong>
</div>
<div class="row">
<ul type="circle">
{{range $item := .Data}}
{{if ne $owner $item.Owner}}
{{$owner = $item.Owner}}
</ul>
</div>
<div class="row">
<strong>{{$owner}}</strong>
</div>
<div class="row">
<ul type="circle">
{{end}}
<li><a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a></li>
{{end}}
</ul>
</div>
{{end}}
{{end}}`
forbidden = `{{define "body"}} forbidden = `{{define "body"}}
<div class="page-header"> <div class="page-header">
<div class="row"> <div class="row">
@ -65,7 +34,7 @@ const (
<head> <head>
<title>GoWiki | {{.Title}}</title> <title>GoWiki | {{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link> <link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-/SZfW8eVqFuioMg+QXacvK1Zc3XLtT5LGu9tMle65Jc="></link> <link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-o0xveJH8qNnWr/39Jnx97yDlYrvjuUnzE4q3p+94Yp4="></link>
<meta name="theme-color" content="#375a7f"> <meta name="theme-color" content="#375a7f">
<meta name="description" content="{{.Title}}"> <meta name="description" content="{{.Title}}">
</head> </head>
@ -166,7 +135,7 @@ const (
menu = `{{define "menu"}} menu = `{{define "menu"}}
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"><a class="nav-link" href="/wiki/Index">Index</a></li> <li class="nav-item"><a class="nav-link" href="/wiki/Index">Index</a></li>
<li class="nav-item"><a class="nav-link" href="/all">All Pages</a></li> <li class="nav-item"><a class="nav-link" href="/sections">Sections</a></li>
{{if .Login}} {{if .Login}}
<li class="nav-item"><a class="nav-link" href="/new">New Page</a></li> <li class="nav-item"><a class="nav-link" href="/new">New Page</a></li>
{{end}} {{end}}
@ -488,6 +457,48 @@ const (
{{end}} {{end}}
</div> </div>
</div> </div>
{{end}}`
section = `{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
{{if .Data}}
<div class="row">
<ul type="circle">
{{range $item := .Data}}
<li><a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a></li>
{{end}}
</ul>
</div>
{{end}}
{{end}}`
sections = `{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
{{if .Data}}
<div class="row">
{{range $index, $item := .Data}}
<div class="col-xs-4 section">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong><a href="/{{$item.Section}}">{{$item.Section}}</a></strong>
</div>
<div class="card-body">
{{range $page := $item.Pages}}
<strong><a href="/{{$page.StoreTitle}}">{{$page.Title}}</a></strong><br>
{{end}}
</div>
</div>
</div>
<div class="offset-1"></div>
{{end}}
</div>
{{end}}
{{end}}` {{end}}`
userDel = `{{define "body"}} userDel = `{{define "body"}}
{{if .Data}} {{if .Data}}
@ -499,7 +510,7 @@ const (
<strong>Delete {{.Data.Username}}?</strong> <strong>Delete {{.Data.Username}}?</strong>
</div> </div>
<div class="card-body"> <div class="card-body">
<p>You are about to delete <a href="/user/edit/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all private pages of this user.</p> <p>You are about to delete user <a href="/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all private pages of this user.</p>
<form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post"> <form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post">
<input type="hidden" name="token" value="{{.Token}}"> <input type="hidden" name="token" value="{{.Token}}">
<button class="btn btn-sm btn-danger" type="submit">Delete</button> <button class="btn btn-sm btn-danger" type="submit">Delete</button>
@ -570,6 +581,7 @@ const (
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
</div>
</div> </div>
</div> </div>
{{end}} {{end}}
@ -659,6 +671,7 @@ const (
<a href="/user" class="btn btn-sm btn-primary">Back</a> <a href="/user" class="btn btn-sm btn-primary">Back</a>
</form> </form>
</div> </div>
</div>
</div> </div>
</div> </div>
{{end}}` {{end}}`
@ -695,6 +708,7 @@ const (
<a href="/user/edit/{{.Data.Username}}" class="btn btn-sm btn-primary">Back</a> <a href="/user/edit/{{.Data.Username}}" class="btn btn-sm btn-primary">Back</a>
</form> </form>
</div> </div>
</div>
</div> </div>
</div> </div>
{{end}} {{end}}
@ -838,6 +852,10 @@ html, body, .container, .content {
.proper-content { .proper-content {
padding-top: 40px; padding-top: 40px;
} }
.section {
margin-bottom: 20px;
width: 250px;
}
.wrapper { .wrapper {
min-height: 100%; min-height: 100%;
height: auto !important; height: auto !important;
@ -883,7 +901,8 @@ func (s *HTTPServer) loadTemplates() {
s.templ["loginHandler"] = parse(index, menu, login) s.templ["loginHandler"] = parse(index, menu, login)
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp) s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
s.templ["searchHandler"] = parse(index, menu, search) s.templ["searchHandler"] = parse(index, menu, search)
s.templ["allHandler"] = parse(index, menu, all) s.templ["sectionsHandler"] = parse(index, menu, sections)
s.templ["sectionHandler"] = parse(index, menu, section)
s.templ["pageHandler"] = parse(index, menu, page) s.templ["pageHandler"] = parse(index, menu, page)
s.templ["pageNewHandler"] = parse(index, menu, pageNew) s.templ["pageNewHandler"] = parse(index, menu, pageNew)
s.templ["pageEditHandler"] = parse(index, menu, pageEdit) s.templ["pageEditHandler"] = parse(index, menu, pageEdit)

View file

@ -74,7 +74,8 @@ func (s *HTTPServer) loadTemplates() {
s.templ["loginHandler"] = parse(index, menu, login) s.templ["loginHandler"] = parse(index, menu, login)
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp) s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
s.templ["searchHandler"] = parse(index, menu, search) s.templ["searchHandler"] = parse(index, menu, search)
s.templ["allHandler"] = parse(index, menu, all) s.templ["sectionsHandler"] = parse(index, menu, sections)
s.templ["sectionHandler"] = parse(index, menu, section)
s.templ["pageHandler"] = parse(index, menu, page) s.templ["pageHandler"] = parse(index, menu, page)
s.templ["pageNewHandler"] = parse(index, menu, pageNew) s.templ["pageNewHandler"] = parse(index, menu, pageNew)
s.templ["pageEditHandler"] = parse(index, menu, pageEdit) s.templ["pageEditHandler"] = parse(index, menu, pageEdit)

View file

@ -125,6 +125,10 @@ html, body, .container, .content {
.proper-content { .proper-content {
padding-top: 40px; padding-top: 40px;
} }
.section {
margin-bottom: 20px;
width: 250px;
}
.wrapper { .wrapper {
min-height: 100%; min-height: 100%;
height: auto !important; height: auto !important;

View file

@ -3,7 +3,7 @@
<head> <head>
<title>GoWiki | {{.Title}}</title> <title>GoWiki | {{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link> <link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-/SZfW8eVqFuioMg+QXacvK1Zc3XLtT5LGu9tMle65Jc="></link> <link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-o0xveJH8qNnWr/39Jnx97yDlYrvjuUnzE4q3p+94Yp4="></link>
<meta name="theme-color" content="#375a7f"> <meta name="theme-color" content="#375a7f">
<meta name="description" content="{{.Title}}"> <meta name="description" content="{{.Title}}">
</head> </head>

View file

@ -1,7 +1,7 @@
{{define "menu"}} {{define "menu"}}
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"><a class="nav-link" href="/wiki/Index">Index</a></li> <li class="nav-item"><a class="nav-link" href="/wiki/Index">Index</a></li>
<li class="nav-item"><a class="nav-link" href="/all">All Pages</a></li> <li class="nav-item"><a class="nav-link" href="/sections">Sections</a></li>
{{if .Login}} {{if .Login}}
<li class="nav-item"><a class="nav-link" href="/new">New Page</a></li> <li class="nav-item"><a class="nav-link" href="/new">New Page</a></li>
{{end}} {{end}}

View file

@ -5,24 +5,9 @@
</div> </div>
</div> </div>
{{if .Data}} {{if .Data}}
<div class="row mt-5">
{{$idx0 := index .Data 0}}
{{$owner := $idx0.Owner}}
<strong>{{$owner}}</strong>
</div>
<div class="row"> <div class="row">
<ul type="circle"> <ul type="circle">
{{range $item := .Data}} {{range $item := .Data}}
{{if ne $owner $item.Owner}}
{{$owner = $item.Owner}}
</ul>
</div>
<div class="row">
<strong>{{$owner}}</strong>
</div>
<div class="row">
<ul type="circle">
{{end}}
<li><a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a></li> <li><a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a></li>
{{end}} {{end}}
</ul> </ul>

26
templates/sections.html Normal file
View file

@ -0,0 +1,26 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
{{if .Data}}
<div class="row">
{{range $index, $item := .Data}}
<div class="col-xs-4 section">
<div class="card border-primary mx-auto">
<div class="card-header">
<strong><a href="/{{$item.Section}}">{{$item.Section}}</a></strong>
</div>
<div class="card-body">
{{range $page := $item.Pages}}
<strong><a href="/{{$page.StoreTitle}}">{{$page.Title}}</a></strong><br>
{{end}}
</div>
</div>
</div>
<div class="offset-1"></div>
{{end}}
</div>
{{end}}
{{end}}

View file

@ -8,7 +8,7 @@
<strong>Delete {{.Data.Username}}?</strong> <strong>Delete {{.Data.Username}}?</strong>
</div> </div>
<div class="card-body"> <div class="card-body">
<p>You are about to delete <a href="/user/edit/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all private pages of this user.</p> <p>You are about to delete user <a href="/{{.Data.Username}}">{{.Data.Username}}</a>.<br>This will also remove all private pages of this user.</p>
<form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post"> <form class="form-horizontal" action="/user/del/{{.Data.Username}}" method="post">
<input type="hidden" name="token" value="{{.Token}}"> <input type="hidden" name="token" value="{{.Token}}">
<button class="btn btn-sm btn-danger" type="submit">Delete</button> <button class="btn btn-sm btn-danger" type="submit">Delete</button>

View file

@ -56,6 +56,7 @@
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
</div>
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -28,6 +28,7 @@
<a href="/user" class="btn btn-sm btn-primary">Back</a> <a href="/user" class="btn btn-sm btn-primary">Back</a>
</form> </form>
</div> </div>
</div>
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -31,6 +31,7 @@
<a href="/user/edit/{{.Data.Username}}" class="btn btn-sm btn-primary">Back</a> <a href="/user/edit/{{.Data.Username}}" class="btn btn-sm btn-primary">Back</a>
</form> </form>
</div> </div>
</div>
</div> </div>
</div> </div>
{{end}} {{end}}