added sections overview
This commit is contained in:
parent
72d00aeb21
commit
799ff0fb2f
16 changed files with 181 additions and 66 deletions
|
|
@ -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) 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 {
|
||||
Username string
|
||||
Password string
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import (
|
|||
)
|
||||
|
||||
var reservedUsers = []string{
|
||||
"all",
|
||||
"blacklist",
|
||||
"login",
|
||||
"logout",
|
||||
"new",
|
||||
"search",
|
||||
"sections",
|
||||
"user",
|
||||
"view",
|
||||
"wiki",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"git.giftfish.de/ston1th/gowiki/pkg/render"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/store"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const pagePrefix = "page/"
|
||||
|
|
@ -21,9 +22,42 @@ var (
|
|||
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 {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -34,6 +68,10 @@ func (db *DB) GetAllPages(username string) (pages core.Pages) {
|
|||
}
|
||||
return nil
|
||||
})
|
||||
if len(pages) == 0 {
|
||||
err = errPageNotFound
|
||||
return
|
||||
}
|
||||
sort.Sort(pages)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,14 +205,31 @@ func searchHandler(ctx *Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func allHandler(ctx *Context) {
|
||||
ctx.Template("allHandler")
|
||||
func sectionsHandler(ctx *Context) {
|
||||
ctx.Template("sectionsHandler")
|
||||
ctx.Data = webData{
|
||||
Title: "All Pages",
|
||||
BodyTitle: "All Pages",
|
||||
Title: "All Sections",
|
||||
BodyTitle: "All Sections",
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ var routes = []route{
|
|||
staticHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/sections",
|
||||
sectionsHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/search",
|
||||
searchHandler,
|
||||
|
|
@ -35,11 +40,6 @@ var routes = []route{
|
|||
pageNewHandler),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/all",
|
||||
allHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/login",
|
||||
loginHandler,
|
||||
|
|
@ -109,6 +109,11 @@ var routes = []route{
|
|||
pageHandler),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}",
|
||||
sectionHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}",
|
||||
pageHandler,
|
||||
|
|
|
|||
|
|
@ -12,37 +12,6 @@ import (
|
|||
)
|
||||
|
||||
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"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
|
|
@ -65,7 +34,7 @@ const (
|
|||
<head>
|
||||
<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="/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="description" content="{{.Title}}">
|
||||
</head>
|
||||
|
|
@ -166,7 +135,7 @@ const (
|
|||
menu = `{{define "menu"}}
|
||||
<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="/all">All Pages</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/sections">Sections</a></li>
|
||||
{{if .Login}}
|
||||
<li class="nav-item"><a class="nav-link" href="/new">New Page</a></li>
|
||||
{{end}}
|
||||
|
|
@ -488,6 +457,48 @@ const (
|
|||
{{end}}
|
||||
</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}}`
|
||||
userDel = `{{define "body"}}
|
||||
{{if .Data}}
|
||||
|
|
@ -499,7 +510,7 @@ const (
|
|||
<strong>Delete {{.Data.Username}}?</strong>
|
||||
</div>
|
||||
<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">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||
|
|
@ -570,6 +581,7 @@ const (
|
|||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -659,6 +671,7 @@ const (
|
|||
<a href="/user" class="btn btn-sm btn-primary">Back</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}`
|
||||
|
|
@ -695,6 +708,7 @@ const (
|
|||
<a href="/user/edit/{{.Data.Username}}" class="btn btn-sm btn-primary">Back</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -838,6 +852,10 @@ html, body, .container, .content {
|
|||
.proper-content {
|
||||
padding-top: 40px;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
width: 250px;
|
||||
}
|
||||
.wrapper {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
|
|
@ -883,7 +901,8 @@ func (s *HTTPServer) loadTemplates() {
|
|||
s.templ["loginHandler"] = parse(index, menu, login)
|
||||
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
|
||||
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["pageNewHandler"] = parse(index, menu, pageNew)
|
||||
s.templ["pageEditHandler"] = parse(index, menu, pageEdit)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,8 @@ func (s *HTTPServer) loadTemplates() {
|
|||
s.templ["loginHandler"] = parse(index, menu, login)
|
||||
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
|
||||
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["pageNewHandler"] = parse(index, menu, pageNew)
|
||||
s.templ["pageEditHandler"] = parse(index, menu, pageEdit)
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ html, body, .container, .content {
|
|||
.proper-content {
|
||||
padding-top: 40px;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
width: 250px;
|
||||
}
|
||||
.wrapper {
|
||||
min-height: 100%;
|
||||
height: auto !important;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<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="/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="description" content="{{.Title}}">
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{{define "menu"}}
|
||||
<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="/all">All Pages</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/sections">Sections</a></li>
|
||||
{{if .Login}}
|
||||
<li class="nav-item"><a class="nav-link" href="/new">New Page</a></li>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -5,24 +5,9 @@
|
|||
</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>
|
||||
26
templates/sections.html
Normal file
26
templates/sections.html
Normal 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}}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<strong>Delete {{.Data.Username}}?</strong>
|
||||
</div>
|
||||
<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">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
<a href="/user" class="btn btn-sm btn-primary">Back</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
<a href="/user/edit/{{.Data.Username}}" class="btn btn-sm btn-primary">Back</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue