Add site-wide navigation on colls when chorus = true
This adds a new config value: `chorus` that signifies an instance is more about the Reader view than individual blogs / writers. When enabled, user navigation will show on all pages, including About, Reader, and Privacy (ref T680). It also uses different collection templates that keep the instance-wide navigation at the top of the page, instead of the author's name -- again, branded more for the collective than the individual. Ref T681
This commit is contained in:
parent
5f28eb55a5
commit
1a80cd3c02
@ -21,6 +21,7 @@ import (
|
||||
"github.com/writeas/web-core/data"
|
||||
"github.com/writeas/web-core/log"
|
||||
"github.com/writeas/writefreely/author"
|
||||
"github.com/writeas/writefreely/config"
|
||||
"github.com/writeas/writefreely/page"
|
||||
"html/template"
|
||||
"net/http"
|
||||
@ -58,11 +59,15 @@ func NewUserPage(app *App, r *http.Request, u *User, title string, flashes []str
|
||||
up.Flashes = flashes
|
||||
up.Path = r.URL.Path
|
||||
up.IsAdmin = u.IsAdmin()
|
||||
up.CanInvite = app.cfg.App.UserInvites != "" &&
|
||||
(up.IsAdmin || app.cfg.App.UserInvites != "admin")
|
||||
up.CanInvite = canUserInvite(app.cfg, up.IsAdmin)
|
||||
return up
|
||||
}
|
||||
|
||||
func canUserInvite(cfg *config.Config, isAdmin bool) bool {
|
||||
return cfg.App.UserInvites != "" &&
|
||||
(isAdmin || cfg.App.UserInvites != "admin")
|
||||
}
|
||||
|
||||
func (up *UserPage) SetMessaging(u *User) {
|
||||
//up.NeedsAuth = app.db.DoesUserNeedAuth(u.ID)
|
||||
}
|
||||
|
2
app.go
2
app.go
@ -317,6 +317,8 @@ func pageForReq(app *App, r *http.Request) page.StaticPage {
|
||||
u = getUserSession(app, r)
|
||||
if u != nil {
|
||||
p.Username = u.Username
|
||||
p.IsAdmin = u != nil && u.IsAdmin()
|
||||
p.CanInvite = canUserInvite(app.cfg, p.IsAdmin)
|
||||
}
|
||||
}
|
||||
p.CanViewReader = !app.cfg.App.Private || u != nil
|
||||
|
@ -525,6 +525,8 @@ type CollectionPage struct {
|
||||
Username string
|
||||
Collections *[]Collection
|
||||
PinnedPosts *[]PublicPost
|
||||
IsAdmin bool
|
||||
CanInvite bool
|
||||
}
|
||||
|
||||
func (c *CollectionObj) ScriptDisplay() template.JS {
|
||||
@ -737,6 +739,8 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||
IsCustomDomain: cr.isCustomDomain,
|
||||
IsWelcome: r.FormValue("greeting") != "",
|
||||
}
|
||||
displayPage.IsAdmin = u != nil && u.IsAdmin()
|
||||
displayPage.CanInvite = canUserInvite(app.cfg, displayPage.IsAdmin)
|
||||
var owner *User
|
||||
if u != nil {
|
||||
displayPage.Username = u.Username
|
||||
@ -768,7 +772,11 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||
// TODO: fix this mess of collections inside collections
|
||||
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj)
|
||||
|
||||
err = templates["collection"].ExecuteTemplate(w, "collection", displayPage)
|
||||
collTmpl := "collection"
|
||||
if app.cfg.App.Chorus {
|
||||
collTmpl = "chorus-collection"
|
||||
}
|
||||
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
|
||||
if err != nil {
|
||||
log.Error("Unable to render collection index: %v", err)
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ type (
|
||||
WebFonts bool `ini:"webfonts"`
|
||||
Landing string `ini:"landing"`
|
||||
SimpleNav bool `ini:"simple_nav"`
|
||||
Chorus bool `ini:"chorus"`
|
||||
|
||||
// Users
|
||||
SingleUser bool `ini:"single_user"`
|
||||
|
@ -28,6 +28,8 @@ type StaticPage struct {
|
||||
Values map[string]string
|
||||
Flashes []string
|
||||
CanViewReader bool
|
||||
IsAdmin bool
|
||||
CanInvite bool
|
||||
}
|
||||
|
||||
// SanitizeHost alters the StaticPage to contain a real hostname. This is
|
||||
|
11
posts.go
11
posts.go
@ -1345,15 +1345,24 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
|
||||
IsPinned bool
|
||||
IsCustomDomain bool
|
||||
PinnedPosts *[]PublicPost
|
||||
IsAdmin bool
|
||||
CanInvite bool
|
||||
}{
|
||||
PublicPost: p,
|
||||
StaticPage: pageForReq(app, r),
|
||||
IsOwner: cr.isCollOwner,
|
||||
IsCustomDomain: cr.isCustomDomain,
|
||||
}
|
||||
tp.IsAdmin = u != nil && u.IsAdmin()
|
||||
tp.CanInvite = canUserInvite(app.cfg, tp.IsAdmin)
|
||||
tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll)
|
||||
tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p)
|
||||
if err := templates["collection-post"].ExecuteTemplate(w, "post", tp); err != nil {
|
||||
|
||||
postTmpl := "collection-post"
|
||||
if app.cfg.App.Chorus {
|
||||
postTmpl = "chorus-collection-post"
|
||||
}
|
||||
if err := templates[postTmpl].ExecuteTemplate(w, "post", tp); err != nil {
|
||||
log.Error("Error in collection-post template: %v", err)
|
||||
}
|
||||
}
|
||||
|
17
read.go
17
read.go
@ -48,6 +48,8 @@ type readPublication struct {
|
||||
CurrentPage int
|
||||
TotalPages int
|
||||
SelTopic string
|
||||
IsAdmin bool
|
||||
CanInvite bool
|
||||
}
|
||||
|
||||
func initLocalTimeline(app *App) {
|
||||
@ -198,11 +200,16 @@ func showLocalTimeline(app *App, w http.ResponseWriter, r *http.Request, page in
|
||||
}
|
||||
|
||||
d := &readPublication{
|
||||
pageForReq(app, r),
|
||||
&posts,
|
||||
page,
|
||||
ttlPages,
|
||||
tag,
|
||||
StaticPage: pageForReq(app, r),
|
||||
Posts: &posts,
|
||||
CurrentPage: page,
|
||||
TotalPages: ttlPages,
|
||||
SelTopic: tag,
|
||||
}
|
||||
if app.cfg.App.Chorus {
|
||||
u := getUserSession(app, r)
|
||||
d.IsAdmin = u != nil && u.IsAdmin()
|
||||
d.CanInvite = canUserInvite(app.cfg, d.IsAdmin)
|
||||
}
|
||||
|
||||
err := templates["read"].ExecuteTemplate(w, "base", d)
|
||||
|
@ -64,11 +64,14 @@ func initTemplate(parentDir, name string) {
|
||||
filepath.Join(parentDir, templatesDir, "include", "footer.tmpl"),
|
||||
filepath.Join(parentDir, templatesDir, "base.tmpl"),
|
||||
}
|
||||
if name == "collection" || name == "collection-tags" {
|
||||
if name == "collection" || name == "collection-tags" || name == "chorus-collection" {
|
||||
// These pages list out collection posts, so we also parse templatesDir + "include/posts.tmpl"
|
||||
files = append(files, filepath.Join(parentDir, templatesDir, "include", "posts.tmpl"))
|
||||
}
|
||||
if name == "collection" || name == "collection-tags" || name == "collection-post" || name == "post" {
|
||||
if name == "chorus-collection" || name == "chorus-collection-post" {
|
||||
files = append(files, filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"))
|
||||
}
|
||||
if name == "collection" || name == "collection-tags" || name == "collection-post" || name == "post" || name == "chorus-collection" || name == "chorus-collection-post" {
|
||||
files = append(files, filepath.Join(parentDir, templatesDir, "include", "post-render.tmpl"))
|
||||
}
|
||||
templates[name] = template.Must(template.New("").Funcs(funcMap).ParseFiles(files...))
|
||||
|
@ -13,14 +13,38 @@
|
||||
<body {{template "body-attrs" .}}>
|
||||
<div id="overlay"></div>
|
||||
<header>
|
||||
<h2><a href="/">{{.SiteName}}</a></h2>
|
||||
{{ if .SimpleNav }}<nav id="full-nav">
|
||||
<div class="left-side">
|
||||
<h2><a href="/">{{.SiteName}}</a></h2>
|
||||
</div>
|
||||
{{ else }}
|
||||
<h2><a href="/">{{.SiteName}}</a></h2>
|
||||
{{ end }}
|
||||
{{if not .SingleUser}}
|
||||
<nav id="user-nav">
|
||||
{{if and .Chorus .Username}}
|
||||
<nav class="dropdown-nav">
|
||||
<ul><li><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/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">
|
||||
<a href="/about"{{if eq .Path "/about"}} class="selected"{{end}}>About</a>
|
||||
{{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}<a href="/read"{{if eq .Path "/read"}} class="selected"{{end}}>Reader</a>{{end}}
|
||||
{{if and (not .SingleUser) (not .Username)}}<a href="/login"{{if eq .Path "/login"}} class="selected"{{end}}>Log in</a>{{else if .SimpleNav}}<a href="/me/logout">Log out</a>{{end}}
|
||||
</nav>
|
||||
{{if .SimpleNav}}{{if .Username}}<div class="right-side" style="font-size: 0.86em;">
|
||||
<a class="simple-btn" href="/new">New Post</a>
|
||||
</div>{{end}}
|
||||
</nav>
|
||||
{{end}}
|
||||
</nav>
|
||||
{{end}}
|
||||
</header>
|
||||
|
150
templates/chorus-collection-post.tmpl
Normal file
150
templates/chorus-collection-post.tmpl
Normal file
@ -0,0 +1,150 @@
|
||||
{{define "post"}}<!DOCTYPE HTML>
|
||||
<html {{if .Language.Valid}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}">
|
||||
<head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}}</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/write.css" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="canonical" href="{{.CanonicalURL}}" />
|
||||
<meta name="generator" content="WriteFreely">
|
||||
<meta name="title" content="{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{if .Collection.Title}}{{.Collection.Title}}{{else}}{{.Collection.Alias}}{{end}}">
|
||||
<meta name="description" content="{{.Summary}}">
|
||||
{{if gt .Views 1}}<meta name="twitter:label1" value="Views">
|
||||
<meta name="twitter:data1" value="{{largeNumFmt .Views}}">{{end}}
|
||||
<meta name="author" content="{{.Collection.Title}}" />
|
||||
<meta itemprop="description" content="{{.Summary}}">
|
||||
<meta itemprop="datePublished" content="{{.CreatedDate}}" />
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:description" content="{{.Summary}}">
|
||||
<meta name="twitter:title" content="{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{if .Collection.Title}}{{.Collection.Title}}{{else}}{{.Collection.Alias}}{{end}}">
|
||||
{{if gt (len .Images) 0}}<meta name="twitter:image" content="{{index .Images 0}}">{{else}}<meta name="twitter:image" content="{{.Collection.AvatarURL}}">{{end}}
|
||||
<meta property="og:title" content="{{.PlainDisplayTitle}}" />
|
||||
<meta property="og:description" content="{{.Summary}}" />
|
||||
<meta property="og:site_name" content="{{.Collection.DisplayTitle}}" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content="{{.CanonicalURL}}" />
|
||||
<meta property="og:updated_time" content="{{.Created8601}}" />
|
||||
{{range .Images}}<meta property="og:image" content="{{.}}" />{{else}}<meta property="og:image" content="{{.Collection.AvatarURL}}">{{end}}
|
||||
<meta property="article:published_time" content="{{.Created8601}}">
|
||||
{{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
|
||||
<style type="text/css">
|
||||
body footer {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
body#post header {
|
||||
padding: 1em 1rem;
|
||||
}
|
||||
article time.dt-published {
|
||||
display: block;
|
||||
color: #666;
|
||||
}
|
||||
body#post article h2#title{
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
article time.dt-published {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{if .Collection.RenderMathJax}}
|
||||
<!-- Add mathjax logic -->
|
||||
{{template "mathjax" . }}
|
||||
{{end}}
|
||||
|
||||
<!-- Add highlighting logic -->
|
||||
{{template "highlighting" .}}
|
||||
|
||||
</head>
|
||||
<body id="post">
|
||||
|
||||
<div id="overlay"></div>
|
||||
|
||||
{{template "user-navigation" .}}
|
||||
|
||||
<article id="post-body" class="{{.Font}} h-entry">{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name">{{.FormattedDisplayTitle}}</h2>{{end}}{{/* TODO: check format: if .Collection.Format.ShowDates*/}}<time class="dt-published" datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</time><div class="e-content">{{.HTMLContent}}</div></article>
|
||||
|
||||
{{ if .Collection.ShowFooterBranding }}
|
||||
<footer dir="ltr">
|
||||
<p style="text-align: left">Published by <a rel="author" href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a>
|
||||
{{ if .IsOwner }} · <span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span>
|
||||
· <a class="xtra-feature" href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a>
|
||||
{{if .IsPinned}} · <a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}}
|
||||
{{ end }}
|
||||
</p>
|
||||
<nav>
|
||||
{{if .PinnedPosts}}
|
||||
{{range .PinnedPosts}}<a class="pinned{{if eq .Slug.String $.Slug.String}} selected{{end}}" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}
|
||||
{{end}}
|
||||
</nav>
|
||||
<hr>
|
||||
<nav><p style="font-size: 0.9em">{{localhtml "published with write.as" .Language.String}}</p></nav>
|
||||
</footer>
|
||||
{{ end }}
|
||||
</body>
|
||||
|
||||
{{if .Collection.CanShowScript}}
|
||||
{{range .Collection.ExternalScripts}}<script type="text/javascript" src="{{.}}" async></script>{{end}}
|
||||
{{if .Collection.Script}}<script type="text/javascript">{{.Collection.ScriptDisplay}}</script>{{end}}
|
||||
{{end}}
|
||||
<script type="text/javascript">
|
||||
|
||||
var pinning = false;
|
||||
function unpinPost(e, postID) {
|
||||
e.preventDefault();
|
||||
if (pinning) {
|
||||
return;
|
||||
}
|
||||
pinning = true;
|
||||
|
||||
var $header = document.getElementsByTagName('header')[0];
|
||||
var callback = function() {
|
||||
// Hide current page
|
||||
var $pinnedNavLink = $header.getElementsByTagName('nav')[0].querySelector('.pinned.selected');
|
||||
$pinnedNavLink.style.display = 'none';
|
||||
};
|
||||
|
||||
var $pinBtn = $header.getElementsByClassName('unpin')[0];
|
||||
$pinBtn.innerHTML = '...';
|
||||
|
||||
var http = new XMLHttpRequest();
|
||||
var url = "/api/collections/{{.Collection.Alias}}/unpin";
|
||||
var params = [ { "id": postID } ];
|
||||
http.open("POST", url, true);
|
||||
http.setRequestHeader("Content-type", "application/json");
|
||||
http.onreadystatechange = function() {
|
||||
if (http.readyState == 4) {
|
||||
pinning = false;
|
||||
if (http.status == 200) {
|
||||
callback();
|
||||
$pinBtn.style.display = 'none';
|
||||
$pinBtn.innerHTML = 'Pin';
|
||||
} else if (http.status == 409) {
|
||||
$pinBtn.innerHTML = 'Unpin';
|
||||
} else {
|
||||
$pinBtn.innerHTML = 'Unpin';
|
||||
alert("Failed to unpin." + (http.status>=500?" Please try again.":""));
|
||||
}
|
||||
}
|
||||
}
|
||||
http.send(JSON.stringify(params));
|
||||
};
|
||||
|
||||
try { // Fonts
|
||||
WebFontConfig = {
|
||||
custom: { families: [ 'Lora:400,700:latin', 'Open+Sans:400,700:latin' ], urls: [ '/css/fonts.css' ] }
|
||||
};
|
||||
(function() {
|
||||
var wf = document.createElement('script');
|
||||
wf.src = '/js/webfont.js';
|
||||
wf.type = 'text/javascript';
|
||||
wf.async = 'true';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(wf, s);
|
||||
})();
|
||||
} catch (e) { /* ¯\_(ツ)_/¯ */ }
|
||||
</script>
|
||||
</html>{{end}}
|
230
templates/chorus-collection.tmpl
Normal file
230
templates/chorus-collection.tmpl
Normal file
@ -0,0 +1,230 @@
|
||||
{{define "collection"}}<!DOCTYPE HTML>
|
||||
<html {{if .Language}}lang="{{.Language}}"{{end}} dir="{{.Direction}}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>{{.DisplayTitle}}{{if not .SingleUser}} — {{.SiteName}}{{end}}</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/write.css" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="canonical" href="{{.CanonicalURL}}">
|
||||
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<link rel="next" href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if not .IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.DisplayTitle}} » Feed" href="{{.CanonicalURL}}feed/" />{{end}}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<meta name="generator" content="WriteFreely">
|
||||
<meta name="description" content="{{.Description}}">
|
||||
<meta itemprop="name" content="{{.DisplayTitle}}">
|
||||
<meta itemprop="description" content="{{.Description}}">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="{{.DisplayTitle}}">
|
||||
<meta name="twitter:image" content="{{.AvatarURL}}">
|
||||
<meta name="twitter:description" content="{{.Description}}">
|
||||
<meta property="og:title" content="{{.DisplayTitle}}" />
|
||||
<meta property="og:site_name" content="{{.DisplayTitle}}" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content="{{.CanonicalURL}}" />
|
||||
<meta property="og:description" content="{{.Description}}" />
|
||||
<meta property="og:image" content="{{.AvatarURL}}">
|
||||
{{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}}
|
||||
<style type="text/css">
|
||||
body#collection header {
|
||||
max-width: 40em;
|
||||
margin: 1em auto;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
}
|
||||
body#collection header.multiuser {
|
||||
max-width: 100%;
|
||||
margin: 1em;
|
||||
}
|
||||
body#collection header nav:not(.pinned-posts) {
|
||||
display: inline;
|
||||
}
|
||||
body#collection header nav.dropdown-nav,
|
||||
body#collection header nav.tabs,
|
||||
body#collection header nav.tabs a:first-child {
|
||||
margin: 0 0 0 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{if .RenderMathJax}}
|
||||
<!-- Add mathjax logic -->
|
||||
{{template "mathjax" .}}
|
||||
{{end}}
|
||||
|
||||
<!-- Add highlighting logic -->
|
||||
{{template "highlighting" . }}
|
||||
|
||||
</head>
|
||||
<body id="collection" itemscope itemtype="http://schema.org/WebPage">
|
||||
{{template "user-navigation" .}}
|
||||
|
||||
<header>
|
||||
<h1 dir="{{.Direction}}" id="blog-title"><a href="/{{if .IsTopLevel}}{{else}}{{.Prefix}}{{.Alias}}/{{end}}" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1>
|
||||
{{if .Description}}<p class="description p-note">{{.Description}}</p>{{end}}
|
||||
{{/*if not .Public/*}}
|
||||
<!--p class="meta-note"><span>Private collection</span>. Only you can see this page.</p-->
|
||||
{{/*end*/}}
|
||||
{{if .PinnedPosts}}<nav class="pinned-posts">
|
||||
{{range .PinnedPosts}}<a class="pinned" href="{{if not $.SingleUser}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}</nav>
|
||||
{{end}}
|
||||
</header>
|
||||
|
||||
{{if .Posts}}<section id="wrapper" itemscope itemtype="http://schema.org/Blog">{{else}}<div id="wrapper">{{end}}
|
||||
|
||||
{{if .IsWelcome}}
|
||||
<div id="welcome">
|
||||
<h2>Welcome, <strong>{{.Username}}</strong>!</h2>
|
||||
<p>This is your new blog.</p>
|
||||
<p><a class="simple-cta" href="/#{{.Alias}}">Start writing</a>, or <a class="simple-cta" href="/me/c/{{.Alias}}">customize</a> your blog.</p>
|
||||
<p>Check out our <a class="simple-cta" href="https://guides.write.as/writing/?pk_campaign=welcome">writing guide</a> to see what else you can do, and <a class="simple-cta" href="/contact">get in touch</a> anytime with questions or feedback.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{template "posts" .}}
|
||||
|
||||
{{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix">
|
||||
{{if or (and .Format.Ascending (lt .CurrentPage .TotalPages)) (isRTL .Direction)}}
|
||||
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">⇠ {{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Previous{{else}}Newer{{end}}</a>{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<a style="float:right;" href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">{{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Next{{else}}Older{{end}} ⇢</a>{{end}}
|
||||
{{else}}
|
||||
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">⇠ Older</a>{{end}}
|
||||
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">Newer ⇢</a>{{end}}
|
||||
{{end}}
|
||||
</nav>{{end}}
|
||||
|
||||
{{if .Posts}}</section>{{else}}</div>{{end}}
|
||||
|
||||
{{if .ShowFooterBranding }}
|
||||
<footer>
|
||||
<hr />
|
||||
<nav dir="ltr">
|
||||
{{if not .SingleUser}}<a class="home pubd" href="/">{{.SiteName}}</a> · {{end}}powered by <a style="margin-left:0" href="https://writefreely.org">writefreely</a>
|
||||
</nav>
|
||||
</footer>
|
||||
{{ end }}
|
||||
</body>
|
||||
|
||||
{{if .CanShowScript}}
|
||||
{{range .ExternalScripts}}<script type="text/javascript" src="{{.}}" async></script>{{end}}
|
||||
{{if .Script}}<script type="text/javascript">{{.ScriptDisplay}}</script>{{end}}
|
||||
{{end}}
|
||||
<script src="/js/h.js"></script>
|
||||
<script src="/js/postactions.js"></script>
|
||||
<script type="text/javascript">
|
||||
var deleting = false;
|
||||
function delPost(e, id, owned) {
|
||||
e.preventDefault();
|
||||
if (deleting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: UNDO!
|
||||
if (window.confirm('Are you sure you want to delete this post?')) {
|
||||
// AJAX
|
||||
deletePost(id, "", function() {
|
||||
// Remove post from list
|
||||
var $postEl = document.getElementById('post-' + id);
|
||||
$postEl.parentNode.removeChild($postEl);
|
||||
// TODO: add next post from this collection at the bottom
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var deletePost = function(postID, token, callback) {
|
||||
deleting = true;
|
||||
|
||||
var $delBtn = document.getElementById('post-' + postID).getElementsByClassName('delete action')[0];
|
||||
$delBtn.innerHTML = '...';
|
||||
|
||||
var http = new XMLHttpRequest();
|
||||
var url = "/api/posts/" + postID;
|
||||
http.open("DELETE", url, true);
|
||||
http.onreadystatechange = function() {
|
||||
if (http.readyState == 4) {
|
||||
deleting = false;
|
||||
if (http.status == 204) {
|
||||
callback();
|
||||
} else if (http.status == 409) {
|
||||
$delBtn.innerHTML = 'delete';
|
||||
alert("Post is synced to another account. Delete the post from that account instead.");
|
||||
// TODO: show "remove" button instead of "delete" now
|
||||
// Persist that state.
|
||||
// Have it remove the post locally only.
|
||||
} else {
|
||||
$delBtn.innerHTML = 'delete';
|
||||
alert("Failed to delete." + (http.status>=500?" Please try again.":""));
|
||||
}
|
||||
}
|
||||
}
|
||||
http.send();
|
||||
};
|
||||
|
||||
var pinning = false;
|
||||
function pinPost(e, postID, slug, title) {
|
||||
e.preventDefault();
|
||||
if (pinning) {
|
||||
return;
|
||||
}
|
||||
pinning = true;
|
||||
|
||||
var callback = function() {
|
||||
// Visibly remove post from collection
|
||||
var $postEl = document.getElementById('post-' + postID);
|
||||
$postEl.parentNode.removeChild($postEl);
|
||||
var $header = document.getElementsByTagName('header')[0];
|
||||
var $pinnedNavs = $header.getElementsByTagName('nav');
|
||||
// Add link to nav
|
||||
var link = '<a class="pinned" href="/{{.Alias}}/'+slug+'">'+title+'</a>';
|
||||
if ($pinnedNavs.length == 0) {
|
||||
$header.insertAdjacentHTML("beforeend", '<nav>'+link+'</nav>');
|
||||
} else {
|
||||
$pinnedNavs[0].insertAdjacentHTML("beforeend", link);
|
||||
}
|
||||
};
|
||||
|
||||
var $pinBtn = document.getElementById('post-' + postID).getElementsByClassName('pin action')[0];
|
||||
$pinBtn.innerHTML = '...';
|
||||
|
||||
var http = new XMLHttpRequest();
|
||||
var url = "/api/collections/{{.Alias}}/pin";
|
||||
var params = [ { "id": postID } ];
|
||||
http.open("POST", url, true);
|
||||
http.setRequestHeader("Content-type", "application/json");
|
||||
http.onreadystatechange = function() {
|
||||
if (http.readyState == 4) {
|
||||
pinning = false;
|
||||
if (http.status == 200) {
|
||||
callback();
|
||||
} else if (http.status == 409) {
|
||||
$pinBtn.innerHTML = 'pin';
|
||||
alert("Post is synced to another account. Delete the post from that account instead.");
|
||||
// TODO: show "remove" button instead of "delete" now
|
||||
// Persist that state.
|
||||
// Have it remove the post locally only.
|
||||
} else {
|
||||
$pinBtn.innerHTML = 'pin';
|
||||
alert("Failed to pin." + (http.status>=500?" Please try again.":""));
|
||||
}
|
||||
}
|
||||
}
|
||||
http.send(JSON.stringify(params));
|
||||
};
|
||||
|
||||
try {
|
||||
WebFontConfig = {
|
||||
custom: { families: [ 'Lora:400,700:latin', 'Open+Sans:400,700:latin' ], urls: [ '/css/fonts.css' ] }
|
||||
};
|
||||
(function() {
|
||||
var wf = document.createElement('script');
|
||||
wf.src = '/js/webfont.js';
|
||||
wf.type = 'text/javascript';
|
||||
wf.async = 'true';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(wf, s);
|
||||
})();
|
||||
} catch (e) {}
|
||||
</script>
|
||||
</html>{{end}}
|
@ -65,11 +65,16 @@
|
||||
}
|
||||
body#collection header nav {
|
||||
display: inline !important;
|
||||
}
|
||||
body#collection header nav:not(#full-nav):not(#user-nav) {
|
||||
margin: 0 0 0 1em !important;
|
||||
}
|
||||
header nav#user-nav {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
body#collection header nav.tabs a:first-child {
|
||||
margin-left: 1em;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "body-attrs"}}id="collection"{{end}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{define "user-navigation"}}
|
||||
<header{{if .SingleUser}} class="singleuser"{{end}}>
|
||||
<header class="{{if .SingleUser}}singleuser{{else}}multiuser{{end}}">
|
||||
{{if .SingleUser}}
|
||||
<nav id="user-nav">
|
||||
<nav class="dropdown-nav">
|
||||
@ -30,6 +30,7 @@
|
||||
<h1><a href="/" title="Return to editor">{{.SiteName}}</a></h1>
|
||||
{{ end }}
|
||||
<nav id="user-nav">
|
||||
{{if .Username}}
|
||||
<nav class="dropdown-nav">
|
||||
<ul><li><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}}
|
||||
@ -41,20 +42,21 @@
|
||||
</ul></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{end}}
|
||||
<nav class="tabs">
|
||||
{{if .SimpleNav}}
|
||||
<a href="/about">About</a>
|
||||
{{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}<a href="/read">Reader</a>{{end}}
|
||||
<a href="/me/logout">Log out</a>
|
||||
{{if .Username}}<a href="/me/logout">Log out</a>{{else}}<a href="/login">Log in</a>{{end}}
|
||||
{{else}}
|
||||
<a href="/me/c/"{{if eq .Path "/me/c/"}} class="selected"{{end}}>Blogs</a>
|
||||
<a href="/me/posts/"{{if eq .Path "/me/posts/"}} class="selected"{{end}}>Drafts</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
</nav>
|
||||
{{if .SimpleNav}}<div class="right-side">
|
||||
{{if .SimpleNav}}{{if .Username}}<div class="right-side">
|
||||
<a class="simple-btn" href="/new">New Post</a>
|
||||
</div>
|
||||
</div>{{end}}
|
||||
</nav>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user