mirror of https://github.com/writeas/writefreely
Merge pull request #157 from writeas/chorus
Reader-first multi-user instances Resolves T680 T681 T684pull/184/head
commit
6b99d75aa9
@ -0,0 +1,235 @@ |
||||
{{define "pad"}}<!DOCTYPE HTML> |
||||
<html> |
||||
<head> |
||||
|
||||
<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} — {{.SiteName}}</title> |
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/write.css" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
|
||||
<meta name="google" value="notranslate"> |
||||
</head> |
||||
<body id="pad" class="light"> |
||||
|
||||
<div id="overlay"></div> |
||||
|
||||
<textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}} |
||||
|
||||
{{end}}{{.Post.Content}}</textarea> |
||||
|
||||
<header id="tools"> |
||||
<div id="clip"> |
||||
{{if not .SingleUser}}<h1>{{if .Chorus}}<a href="/" title="Home">{{else}}<a href="/me/c/" title="View blogs">{{end}}{{.SiteName}}</a></h1>{{end}} |
||||
<nav id="target" {{if .SingleUser}}style="margin-left:0"{{end}}><ul> |
||||
<li>{{if .Blogs}}<a href="{{$c := index .Blogs 0}}{{$c.CanonicalURL}}">My Posts</a>{{else}}<a>Draft</a>{{end}}</li> |
||||
</ul></nav> |
||||
<span id="wc" class="hidden if-room room-4">0 words</span> |
||||
</div> |
||||
<noscript style="margin-left: 2em;"><strong>NOTE</strong>: for now, you'll need Javascript enabled to post.</noscript> |
||||
<div id="belt"> |
||||
{{if .Editing}}<div class="tool hidden if-room"><a href="{{if .EditCollection}}{{.EditCollection.CanonicalURL}}{{.Post.Slug}}/edit/meta{{else}}/{{if .SingleUser}}d/{{end}}{{.Post.Id}}/meta{{end}}" title="Edit post metadata" id="edit-meta"><img class="ic-24dp" src="/img/ic_info_dark@2x.png" /></a></div>{{end}} |
||||
<div class="tool"><button title="Publish your writing" id="publish" style="font-weight: bold">Post</button></div> |
||||
</div> |
||||
</header> |
||||
|
||||
<script src="/js/h.js"></script> |
||||
<script> |
||||
var $writer = H.getEl('writer'); |
||||
var $btnPublish = H.getEl('publish'); |
||||
var $wc = H.getEl("wc"); |
||||
var updateWordCount = function() { |
||||
var words = 0; |
||||
var val = $writer.el.value.trim(); |
||||
if (val != '') { |
||||
words = $writer.el.value.trim().replace(/\s+/gi, ' ').split(' ').length; |
||||
} |
||||
$wc.el.innerText = words + " word" + (words != 1 ? "s" : ""); |
||||
}; |
||||
var setButtonStates = function() { |
||||
if (!canPublish) { |
||||
$btnPublish.el.className = 'disabled'; |
||||
return; |
||||
} |
||||
if ($writer.el.value.length === 0 || (draftDoc != 'lastDoc' && $writer.el.value == origDoc)) { |
||||
$btnPublish.el.className = 'disabled'; |
||||
} else { |
||||
$btnPublish.el.className = ''; |
||||
} |
||||
}; |
||||
{{if .Post.Id}}var draftDoc = 'draft{{.Post.Id}}'; |
||||
var origDoc = '{{.Post.Content}}';{{else}}var draftDoc = 'lastDoc';{{end}} |
||||
H.load($writer, draftDoc, true); |
||||
updateWordCount(); |
||||
|
||||
var typingTimer; |
||||
var doneTypingInterval = 200; |
||||
|
||||
var posts; |
||||
{{if and .Post.Id (not .Post.Slug)}} |
||||
var token = null; |
||||
var curPostIdx; |
||||
posts = JSON.parse(H.get('posts', '[]')); |
||||
for (var i=0; i<posts.length; i++) { |
||||
if (posts[i].id == "{{.Post.Id}}") { |
||||
token = posts[i].token; |
||||
break; |
||||
} |
||||
} |
||||
var canPublish = token != null; |
||||
{{else}}var canPublish = true;{{end}} |
||||
var publishing = false; |
||||
var justPublished = false; |
||||
|
||||
var publish = function(content, font) { |
||||
{{if and (and .Post.Id (not .Post.Slug)) (not .User)}} |
||||
if (!token) { |
||||
alert("You don't have permission to update this post."); |
||||
return; |
||||
} |
||||
{{end}} |
||||
publishing = true; |
||||
$btnPublish.el.textContent = 'Posting...'; |
||||
$btnPublish.el.disabled = true; |
||||
|
||||
var http = new XMLHttpRequest(); |
||||
var lang = navigator.languages ? navigator.languages[0] : (navigator.language || navigator.userLanguage); |
||||
lang = lang.substring(0, 2); |
||||
var post = H.getTitleStrict(content); |
||||
|
||||
var params = { |
||||
body: post.content, |
||||
title: post.title, |
||||
font: font, |
||||
lang: lang |
||||
}; |
||||
{{ if .Post.Slug }} |
||||
var url = "/api/collections/{{.EditCollection.Alias}}/posts/{{.Post.Id}}"; |
||||
{{ else if .Post.Id }} |
||||
var url = "/api/posts/{{.Post.Id}}"; |
||||
if (typeof token === 'undefined' || !token) { |
||||
token = ""; |
||||
} |
||||
params.token = token; |
||||
{{ else }} |
||||
var url = "/api/posts"; |
||||
var postTarget = '{{if .Blogs}}{{$c := index .Blogs 0}}{{$c.Alias}}{{else}}anonymous{{end}}'; |
||||
if (postTarget != 'anonymous') { |
||||
url = "/api/collections/" + postTarget + "/posts"; |
||||
} |
||||
{{ end }} |
||||
|
||||
http.open("POST", url, true); |
||||
|
||||
// Send the proper header information along with the request |
||||
http.setRequestHeader("Content-type", "application/json"); |
||||
|
||||
http.onreadystatechange = function() { |
||||
if (http.readyState == 4) { |
||||
publishing = false; |
||||
if (http.status == 200 || http.status == 201) { |
||||
data = JSON.parse(http.responseText); |
||||
id = data.data.id; |
||||
nextURL = '{{if .SingleUser}}/d{{end}}/'+id; |
||||
|
||||
{{ if not .Post.Id }} |
||||
// Post created |
||||
if (postTarget != 'anonymous') { |
||||
nextURL = {{if not .SingleUser}}'/'+postTarget+{{end}}'/'+data.data.slug; |
||||
} |
||||
editToken = data.data.token; |
||||
|
||||
{{ if not .User }}if (postTarget == 'anonymous') { |
||||
// Save the data |
||||
var posts = JSON.parse(H.get('posts', '[]')); |
||||
|
||||
{{if .Post.Id}}var newPost = H.createPost("{{.Post.Id}}", token, content); |
||||
for (var i=0; i<posts.length; i++) { |
||||
if (posts[i].id == "{{.Post.Id}}") { |
||||
posts[i].title = newPost.title; |
||||
posts[i].summary = newPost.summary; |
||||
break; |
||||
} |
||||
} |
||||
nextURL = "/pad/posts";{{else}}posts.push(H.createPost(id, editToken, content));{{end}} |
||||
|
||||
H.set('posts', JSON.stringify(posts)); |
||||
} |
||||
{{ end }} |
||||
{{ end }} |
||||
|
||||
justPublished = true; |
||||
if (draftDoc != 'lastDoc') { |
||||
H.remove(draftDoc); |
||||
{{if .Editing}}H.remove('draft{{.Post.Id}}font');{{end}} |
||||
} else { |
||||
H.set(draftDoc, ''); |
||||
} |
||||
|
||||
{{if .EditCollection}} |
||||
window.location = '{{.EditCollection.CanonicalURL}}{{.Post.Slug}}'; |
||||
{{else}} |
||||
window.location = nextURL; |
||||
{{end}} |
||||
} else { |
||||
$btnPublish.el.textContent = 'Post'; |
||||
alert("Failed to post. Please try again."); |
||||
} |
||||
} |
||||
} |
||||
http.send(JSON.stringify(params)); |
||||
}; |
||||
|
||||
setButtonStates(); |
||||
$writer.on('keyup input', function() { |
||||
setButtonStates(); |
||||
clearTimeout(typingTimer); |
||||
typingTimer = setTimeout(doneTyping, doneTypingInterval); |
||||
}, false); |
||||
$writer.on('keydown', function(e) { |
||||
clearTimeout(typingTimer); |
||||
if (e.keyCode == 13 && (e.metaKey || e.ctrlKey)) { |
||||
$btnPublish.el.click(); |
||||
} |
||||
}); |
||||
$btnPublish.on('click', function(e) { |
||||
e.preventDefault(); |
||||
if (!publishing && $writer.el.value) { |
||||
var content = $writer.el.value; |
||||
publish(content, selectedFont); |
||||
} |
||||
}); |
||||
|
||||
WebFontConfig = { |
||||
custom: { families: [ 'Lora:400,700:latin' ], urls: [ '/css/fonts.css' ] } |
||||
}; |
||||
var selectedFont = H.get('{{if .Editing}}draft{{.Post.Id}}font{{else}}padFont{{end}}', '{{.Post.Font}}'); |
||||
|
||||
var doneTyping = function() { |
||||
if (draftDoc == 'lastDoc' || $writer.el.value != origDoc) { |
||||
H.save($writer, draftDoc); |
||||
updateWordCount(); |
||||
} |
||||
}; |
||||
window.addEventListener('beforeunload', function(e) { |
||||
if (draftDoc != 'lastDoc' && $writer.el.value == origDoc) { |
||||
H.remove(draftDoc); |
||||
} else if (!justPublished) { |
||||
doneTyping(); |
||||
} |
||||
}); |
||||
|
||||
try { |
||||
(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) { |
||||
// whatevs |
||||
} |
||||
</script> |
||||
</body> |
||||
</html>{{end}} |
@ -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 $footer = document.getElementsByTagName('footer')[0]; |
||||
var callback = function() { |
||||
// Hide current page |
||||
var $pinnedNavLink = $footer.getElementsByTagName('nav')[0].querySelector('.pinned.selected'); |
||||
$pinnedNavLink.style.display = 'none'; |
||||
}; |
||||
|
||||
var $pinBtn = $footer.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.querySelector('header:not(.multiuser)'); |
||||
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