mirror of https://github.com/writeas/writefreely
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 T681customize-reader
parent
5f28eb55a5
commit
1a80cd3c02
@ -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}} |
@ -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}} |
Loading…
Reference in new issue