Navigation improvementspull/376/head
@@ -49,6 +49,7 @@ type ( | |||||
Separator template.HTML | Separator template.HTML | ||||
IsAdmin bool | IsAdmin bool | ||||
CanInvite bool | CanInvite bool | ||||
CollAlias string | |||||
} | } | ||||
) | ) | ||||
@@ -840,6 +841,7 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques | |||||
Collection: c, | Collection: c, | ||||
Silenced: silenced, | Silenced: silenced, | ||||
} | } | ||||
obj.UserPage.CollAlias = c.Alias | |||||
showUserPage(w, "collection", obj) | showUserPage(w, "collection", obj) | ||||
return nil | return nil | ||||
@@ -1019,6 +1021,7 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error | |||||
TopPosts: topPosts, | TopPosts: topPosts, | ||||
Silenced: silenced, | Silenced: silenced, | ||||
} | } | ||||
obj.UserPage.CollAlias = c.Alias | |||||
if app.cfg.App.Federation { | if app.cfg.App.Federation { | ||||
folls, err := app.db.GetAPFollowers(c) | folls, err := app.db.GetAPFollowers(c) | ||||
if err != nil { | if err != nil { | ||||
@@ -32,6 +32,19 @@ nav#admin { | |||||
display: flex; | display: flex; | ||||
justify-content: center; | justify-content: center; | ||||
&:not(.pages) { | |||||
display: block; | |||||
margin: 0.5em 0; | |||||
a { | |||||
margin-left: 0; | |||||
.rounded(.25em); | |||||
&+a { | |||||
margin-left: 0.5em; | |||||
} | |||||
} | |||||
} | |||||
a { | a { | ||||
color: #333; | color: #333; | ||||
font-family: @sansFont; | font-family: @sansFont; | ||||
@@ -10,6 +10,8 @@ | |||||
@proSelectedCol: #71D571; | @proSelectedCol: #71D571; | ||||
@textLinkColor: rgb(0, 0, 238); | @textLinkColor: rgb(0, 0, 238); | ||||
@accent: #767676; | |||||
body { | body { | ||||
font-family: @serifFont; | font-family: @serifFont; | ||||
font-size-adjust: 0.5; | font-size-adjust: 0.5; | ||||
@@ -743,6 +745,18 @@ input, button, select.inputform, textarea.inputform, a.btn { | |||||
} | } | ||||
} | } | ||||
.btn.pager { | |||||
border: 1px solid @lightNavBorder; | |||||
font-size: .86em; | |||||
padding: .5em 1em; | |||||
white-space: nowrap; | |||||
font-family: @sansFont; | |||||
&:hover { | |||||
text-decoration: none; | |||||
background: @lightNavBorder; | |||||
} | |||||
} | |||||
div.flat-select { | div.flat-select { | ||||
display: inline-block; | display: inline-block; | ||||
position: relative; | position: relative; | ||||
@@ -965,7 +979,12 @@ footer.contain-me { | |||||
} | } | ||||
ul { | ul { | ||||
&.collections { | &.collections { | ||||
padding-left: 0; | |||||
margin-left: 0; | margin-left: 0; | ||||
h3 { | |||||
margin-top: 0; | |||||
font-weight: normal; | |||||
} | |||||
li { | li { | ||||
&.collection { | &.collection { | ||||
a.title { | a.title { | ||||
@@ -1095,7 +1114,8 @@ body#pad-sub #posts, .atoms { | |||||
} | } | ||||
.electron { | .electron { | ||||
font-weight: normal; | font-weight: normal; | ||||
margin-left: 0.5em; | |||||
font-size: 0.86em; | |||||
margin-left: 0.75rem; | |||||
} | } | ||||
} | } | ||||
h3, h4 { | h3, h4 { | ||||
@@ -1245,7 +1265,7 @@ header { | |||||
} | } | ||||
} | } | ||||
&.singleuser { | &.singleuser { | ||||
margin: 0.5em 0.25em; | |||||
margin: 0.5em 1em 0.5em 0.25em; | |||||
nav#user-nav { | nav#user-nav { | ||||
nav > ul > li:first-child { | nav > ul > li:first-child { | ||||
img { | img { | ||||
@@ -1253,6 +1273,9 @@ header { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
.right-side { | |||||
padding-top: 0.5em; | |||||
} | |||||
} | } | ||||
.dash-nav { | .dash-nav { | ||||
font-weight: bold; | font-weight: bold; | ||||
@@ -1547,3 +1570,26 @@ div.row { | |||||
pre.code-block { | pre.code-block { | ||||
overflow-x: auto; | overflow-x: auto; | ||||
} | } | ||||
#org-nav { | |||||
font-family: @sansFont; | |||||
font-size: 1.1em; | |||||
color: #888; | |||||
em, strong { | |||||
color: #000; | |||||
} | |||||
&+h1 { | |||||
margin-top: 0.5em; | |||||
} | |||||
a:link, a:visited, a:hover { | |||||
color: @accent; | |||||
} | |||||
a:first-child { | |||||
margin-right: 0.25em; | |||||
} | |||||
a.coll-name { | |||||
font-weight: bold; | |||||
margin-left: 0.25em; | |||||
} | |||||
} |
@@ -127,7 +127,6 @@ textarea { | |||||
&.collection { | &.collection { | ||||
a.title { | a.title { | ||||
font-size: 1.3em; | font-size: 1.3em; | ||||
font-weight: bold; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -11,6 +11,7 @@ | |||||
package writefreely | package writefreely | ||||
import ( | import ( | ||||
"errors" | |||||
"html/template" | "html/template" | ||||
"io" | "io" | ||||
"io/ioutil" | "io/ioutil" | ||||
@@ -38,6 +39,9 @@ var ( | |||||
"localhtml": localHTML, | "localhtml": localHTML, | ||||
"tolower": strings.ToLower, | "tolower": strings.ToLower, | ||||
"title": strings.Title, | "title": strings.Title, | ||||
"hasPrefix": strings.HasPrefix, | |||||
"hasSuffix": strings.HasSuffix, | |||||
"dict": dict, | |||||
} | } | ||||
) | ) | ||||
@@ -109,6 +113,7 @@ func initUserPage(parentDir, path, key string) { | |||||
filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"), | filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"), | ||||
filepath.Join(parentDir, templatesDir, "user", "include", "footer.tmpl"), | filepath.Join(parentDir, templatesDir, "user", "include", "footer.tmpl"), | ||||
filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"), | filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"), | ||||
filepath.Join(parentDir, templatesDir, "user", "include", "nav.tmpl"), | |||||
)) | )) | ||||
} | } | ||||
@@ -206,3 +211,19 @@ func localHTML(term, lang string) template.HTML { | |||||
s = strings.Replace(s, "write.as", "<a href=\"https://writefreely.org\">writefreely</a>", 1) | s = strings.Replace(s, "write.as", "<a href=\"https://writefreely.org\">writefreely</a>", 1) | ||||
return template.HTML(s) | return template.HTML(s) | ||||
} | } | ||||
// from: https://stackoverflow.com/a/18276968/1549194 | |||||
func dict(values ...interface{}) (map[string]interface{}, error) { | |||||
if len(values)%2 != 0 { | |||||
return nil, errors.New("dict: invalid number of parameters") | |||||
} | |||||
dict := make(map[string]interface{}, len(values)/2) | |||||
for i := 0; i < len(values); i += 2 { | |||||
key, ok := values[i].(string) | |||||
if !ok { | |||||
return nil, errors.New("dict: keys must be strings") | |||||
} | |||||
dict[key] = values[i+1] | |||||
} | |||||
return dict, nil | |||||
} |
@@ -26,7 +26,7 @@ | |||||
{{end}} | {{end}} | ||||
</table> | </table> | ||||
<nav class="pager"> | |||||
<nav class="pager pages"> | |||||
{{range $n := .TotalPages}}<a href="/admin/users{{if ne $n 1}}?p={{$n}}{{end}}" {{if eq $.CurPage $n}}class="selected"{{end}}>{{$n}}</a>{{end}} | {{range $n := .TotalPages}}<a href="/admin/users{{if ne $n 1}}?p={{$n}}{{end}}" {{if eq $.CurPage $n}}class="selected"{{end}}>{{$n}}</a>{{end}} | ||||
</nav> | </nav> | ||||
@@ -20,7 +20,12 @@ textarea.section.norm { | |||||
{{if .Silenced}} | {{if .Silenced}} | ||||
{{template "user-silenced"}} | {{template "user-silenced"}} | ||||
{{end}} | {{end}} | ||||
<h2>Customize {{.DisplayTitle}} <a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">view blog</a></h2> | |||||
{{template "collection-breadcrumbs" .}} | |||||
<h1>Customize</h1> | |||||
{{template "collection-nav" (dict "Alias" .Alias "Path" .Path "SingleUser" .SingleUser)}} | |||||
{{if .Flashes}}<ul class="errors"> | {{if .Flashes}}<ul class="errors"> | ||||
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | {{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | ||||
@@ -12,16 +12,18 @@ | |||||
{{end}} | {{end}} | ||||
<h1>Blogs</h1> | <h1>Blogs</h1> | ||||
<ul class="atoms collections"> | <ul class="atoms collections"> | ||||
{{range $i, $el := .Collections}}<li class="collection"><h3> | |||||
<a class="title" href="/{{.Alias}}/">{{if .Title}}{{.Title}}{{else}}{{.Alias}}{{end}}</a> | |||||
</h3> | |||||
<h4> | |||||
<a class="action new-post" href="{{if $.Chorus}}/new{{else}}/{{end}}#{{.Alias}}">new post</a> | |||||
<a class="action" href="/me/c/{{.Alias}}">customize</a> | |||||
<a class="action" href="/me/c/{{.Alias}}/stats">stats</a> | |||||
</h4> | |||||
{{if .Description}}<p class="description">{{.Description}}</p>{{end}} | |||||
</li>{{end}} | |||||
{{range $i, $el := .Collections}}<li class="collection"> | |||||
<div class="row lineitem"> | |||||
<div> | |||||
<h3> | |||||
<a class="title" href="/{{.Alias}}/" >{{if .Title}}{{.Title}}{{else}}{{.Alias}}{{end}}</a> | |||||
<span class="electron" {{if .IsPrivate}}style="font-style: italic"{{end}}>{{if .IsPrivate}}private{{else}}{{.DisplayCanonicalURL}}{{end}}</span> | |||||
</h3> | |||||
{{template "collection-nav" (dict "Alias" .Alias "Path" $.Path "SingleUser" $.SingleUser "CanPost" true )}} | |||||
{{if .Description}}<p class="description">{{.Description}}</p>{{end}} | |||||
</div> | |||||
</div> | |||||
</li>{{end}} | |||||
<li id="create-collection"> | <li id="create-collection"> | ||||
{{if not .NewBlogsDisabled}} | {{if not .NewBlogsDisabled}} | ||||
<form method="POST" action="/api/collections" id="new-collection-form" onsubmit="return createCollection()"> | <form method="POST" action="/api/collections" id="new-collection-form" onsubmit="return createCollection()"> | ||||
@@ -1,76 +1,78 @@ | |||||
{{define "user-navigation"}} | {{define "user-navigation"}} | ||||
<header class="{{if .SingleUser}}singleuser{{else}}multiuser{{end}}"> | <header class="{{if .SingleUser}}singleuser{{else}}multiuser{{end}}"> | ||||
<nav id="full-nav"> | |||||
{{if .SingleUser}} | {{if .SingleUser}} | ||||
<nav id="user-nav"> | |||||
<nav class="dropdown-nav"> | |||||
<ul><li><a href="/" title="View blog" class="title">{{.SiteName}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /> | |||||
<ul> | |||||
<li><a href="/me/c/{{.Username}}">Customize</a></li> | |||||
<li><a href="/me/c/{{.Username}}/stats">Stats</a></li> | |||||
<li class="separator"><hr /></li> | |||||
{{if .IsAdmin}}<li><a href="/admin">Admin</a></li>{{end}} | |||||
<li><a href="/me/settings">Settings</a></li> | |||||
<li><a href="/me/import">Import posts</a></li> | |||||
<li><a href="/me/export">Export</a></li> | |||||
<li class="separator"><hr /></li> | |||||
<li><a href="/me/logout">Log out</a></li> | |||||
</ul></li> | |||||
</ul> | |||||
</nav> | |||||
<nav class="tabs"> | |||||
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a> | |||||
<a href="/me/new">New Post</a> | |||||
<nav id="user-nav"> | |||||
<nav class="dropdown-nav"> | |||||
<ul><li><a href="/" title="View blog" class="title">{{.SiteName}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /> | |||||
<ul> | |||||
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}} | |||||
<li><a href="/me/settings">Account settings</a></li> | |||||
<li><a href="/me/import">Import posts</a></li> | |||||
<li><a href="/me/export">Export</a></li> | |||||
<li class="separator"><hr /></li> | |||||
<li><a href="/me/logout">Log out</a></li> | |||||
</ul></li> | |||||
</ul> | |||||
</nav> | |||||
<nav class="tabs"> | |||||
<a href="/me/c/{{.Username}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Username)}}class="selected"{{end}}>Customize</a> | |||||
<a href="/me/c/{{.Username}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>Stats</a> | |||||
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a> | |||||
</nav> | |||||
</nav> | </nav> | ||||
</nav> | |||||
<div class="right-side"> | |||||
<a class="simple-btn" href="/me/new">New Post</a> | |||||
</div> | |||||
{{else}} | {{else}} | ||||
<nav id="full-nav"> | |||||
<div class="left-side"> | <div class="left-side"> | ||||
<h1><a href="/" title="Return to editor">{{.SiteName}}</a></h1> | <h1><a href="/" title="Return to editor">{{.SiteName}}</a></h1> | ||||
</div> | </div> | ||||
<nav id="user-nav"> | |||||
{{if .Username}} | |||||
<nav class="dropdown-nav"> | |||||
<ul><li class="has-submenu"><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul> | |||||
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}} | |||||
<li><a href="/me/settings">Account settings</a></li> | |||||
<li><a href="/me/import">Import posts</a></li> | |||||
<li><a href="/me/export">Export</a></li> | |||||
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}} | |||||
<li class="separator"><hr /></li> | |||||
<li><a href="/me/logout">Log out</a></li> | |||||
</ul></li> | |||||
</ul> | |||||
</nav> | |||||
{{end}} | |||||
<nav class="tabs"> | |||||
{{if .SimpleNav}} | |||||
{{ if not .SingleUser }} | |||||
{{if and (and .LocalTimeline .CanViewReader) .Chorus}}<a href="/"{{if eq .Path "/"}} class="selected"{{end}}>Home</a>{{end}} | |||||
{{ end }} | |||||
<a href="/about">About</a> | |||||
{{ if not .SingleUser }} | |||||
{{ if .Username }} | |||||
{{if gt .MaxBlogs 1}}<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a>{{end}} | |||||
{{if and .Chorus (eq .MaxBlogs 1)}}<a href="/{{.Username}}/"{{if eq .Path (printf "/%s/" .Username)}} class="selected"{{end}}>My Posts</a>{{end}} | |||||
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}} | |||||
{{ end }} | |||||
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">Reader</a>{{end}} | |||||
{{if and (and (and .Chorus .OpenRegistration) (not .Username)) (or (not .Private) (ne .Landing ""))}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>Sign up</a>{{end}} | |||||
{{if .Username}}<a href="/me/logout">Log out</a>{{else}}<a href="/login">Log in</a>{{end}} | |||||
{{ end }} | |||||
{{else}} | |||||
<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a> | |||||
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}} | |||||
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">Reader</a>{{end}} | |||||
<nav id="user-nav"> | |||||
{{if .Username}} | |||||
<nav class="dropdown-nav"> | |||||
<ul><li class="has-submenu"><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul> | |||||
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}} | |||||
<li><a href="/me/settings">Account settings</a></li> | |||||
<li><a href="/me/import">Import posts</a></li> | |||||
<li><a href="/me/export">Export</a></li> | |||||
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}} | |||||
<li class="separator"><hr /></li> | |||||
<li><a href="/me/logout">Log out</a></li> | |||||
</ul></li> | |||||
</ul> | |||||
</nav> | |||||
{{end}} | {{end}} | ||||
<nav class="tabs"> | |||||
{{if .SimpleNav}} | |||||
{{ if not .SingleUser }} | |||||
{{if and (and .LocalTimeline .CanViewReader) .Chorus}}<a href="/"{{if eq .Path "/"}} class="selected"{{end}}>Home</a>{{end}} | |||||
{{ end }} | |||||
<a href="/about">About</a> | |||||
{{ if not .SingleUser }} | |||||
{{ if .Username }} | |||||
{{if gt .MaxBlogs 1}}<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a>{{end}} | |||||
{{if and .Chorus (eq .MaxBlogs 1)}}<a href="/{{.Username}}/"{{if eq .Path (printf "/%s/" .Username)}} class="selected"{{end}}>My Posts</a>{{end}} | |||||
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}} | |||||
{{ end }} | |||||
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">Reader</a>{{end}} | |||||
{{if and (and (and .Chorus .OpenRegistration) (not .Username)) (or (not .Private) (ne .Landing ""))}}<a href="/signup"{{if eq .Path "/signup"}} class="selected"{{end}}>Sign up</a>{{end}} | |||||
{{if .Username}}<a href="/me/logout">Log out</a>{{else}}<a href="/login">Log in</a>{{end}} | |||||
{{ end }} | |||||
{{else}} | |||||
<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a> | |||||
{{if not .DisableDrafts}}<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>{{end}} | |||||
{{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}<a href="/read">Reader</a>{{end}} | |||||
{{end}} | |||||
</nav> | |||||
</nav> | </nav> | ||||
</nav> | |||||
{{if .Chorus}}{{if .Username}}<div class="right-side"> | |||||
<a class="simple-btn" href="/new">New Post</a> | |||||
</div>{{end}} | |||||
</nav> | |||||
{{end}} | |||||
{{if .Username}} | |||||
<div class="right-side"> | |||||
<a class="simple-btn" href="/{{if .CollAlias}}#{{.CollAlias}}{{end}}">New Post</a> | |||||
</div> | |||||
{{end}} | |||||
{{end}} | {{end}} | ||||
</nav> | |||||
</header> | </header> | ||||
{{end}} | {{end}} | ||||
{{define "header"}}<!DOCTYPE HTML> | {{define "header"}}<!DOCTYPE HTML> | ||||
@@ -0,0 +1,16 @@ | |||||
{{define "collection-breadcrumbs"}} | |||||
{{if and .Collection (not .SingleUser)}}<nav id="org-nav"><a href="/me/c/">Blogs</a> / <a class="coll-name" href="/{{.Collection.Alias}}/">{{.Collection.DisplayTitle}}</a></nav>{{end}} | |||||
{{end}} | |||||
{{define "collection-nav"}} | |||||
{{if not .SingleUser}} | |||||
<header class="admin"> | |||||
<nav class="pager"> | |||||
{{if .CanPost}}<a href="{{if .SingleUser}}/me/new{{else}}/#{{.Alias}}{{end}}" class="btn gentlecta">New Post</a>{{end}} | |||||
<a href="/me/c/{{.Alias}}" {{if and (hasPrefix .Path "/me/c/") (hasSuffix .Path .Alias)}}class="selected"{{end}}>Customize</a> | |||||
<a href="/me/c/{{.Alias}}/stats" {{if hasSuffix .Path "/stats"}}class="selected"{{end}}>Stats</a> | |||||
<a href="{{if .SingleUser}}/{{else}}/{{.Alias}}/{{end}}">View Blog →</a> | |||||
</nav> | |||||
</header> | |||||
{{end}} | |||||
{{end}} |
@@ -16,7 +16,7 @@ h3 { font-weight: normal; } | |||||
{{if .Silenced}} | {{if .Silenced}} | ||||
{{template "user-silenced"}} | {{template "user-silenced"}} | ||||
{{end}} | {{end}} | ||||
<h1>{{if .IsLogOut}}Before you go...{{else}}Account Settings {{if .IsAdmin}}<a href="/admin">admin settings</a>{{end}}{{end}}</h1> | |||||
<h1>{{if .IsLogOut}}Before you go...{{else}}Account Settings{{end}}</h1> | |||||
{{if .Flashes}}<ul class="errors"> | {{if .Flashes}}<ul class="errors"> | ||||
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | {{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} | ||||
</ul>{{end}} | </ul>{{end}} | ||||
@@ -20,7 +20,14 @@ td.none { | |||||
{{if .Silenced}} | {{if .Silenced}} | ||||
{{template "user-silenced"}} | {{template "user-silenced"}} | ||||
{{end}} | {{end}} | ||||
<h2 id="posts-header">{{if .Collection}}{{.Collection.DisplayTitle}} {{end}}Stats</h2> | |||||
{{template "collection-breadcrumbs" .}} | |||||
<h1 id="posts-header">Stats</h1> | |||||
{{if .Collection}} | |||||
{{template "collection-nav" (dict "Alias" .Collection.Alias "Path" .Path "SingleUser" .SingleUser)}} | |||||
{{end}} | |||||
<p>Stats for all time.</p> | <p>Stats for all time.</p> | ||||