Page MenuHomeMusing Studio

No OneTemporary

diff --git a/static/js/posts.js b/static/js/posts.js
index ef3143d..e96aa79 100644
--- a/static/js/posts.js
+++ b/static/js/posts.js
@@ -1,323 +1,330 @@
/**
* Functionality for managing local Write.as posts.
*
* Dependencies:
* h.js
*/
function toggleTheme() {
var btns;
try {
btns = Array.prototype.slice.call(document.getElementById('belt').querySelectorAll('.tool img'));
} catch (e) {}
if (document.body.className == 'light') {
document.body.className = 'dark';
try {
for (var i=0; i<btns.length; i++) {
btns[i].src = btns[i].src.replace('_dark@2x.png', '@2x.png');
}
} catch (e) {}
} else if (document.body.className == 'dark') {
document.body.className = 'light';
try {
for (var i=0; i<btns.length; i++) {
btns[i].src = btns[i].src.replace('@2x.png', '_dark@2x.png');
}
} catch (e) {}
} else {
// Don't alter the theme
return;
}
H.set('padTheme', document.body.className);
}
if (H.get('padTheme', 'light') != 'light') {
toggleTheme();
}
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?')) {
var token;
for (var i=0; i<posts.length; i++) {
if (posts[i].id == id) {
token = posts[i].token;
break;
}
}
if (owned || token) {
// AJAX
deletePost(id, token, function() {
// Remove post from list
var $postEl = document.getElementById('post-' + id);
$postEl.parentNode.removeChild($postEl);
if (posts.length == 0) {
displayNoPosts();
return;
}
// Fill in full page of posts
var $postsChildren = $posts.el.getElementsByClassName('post');
if ($postsChildren.length < postsPerPage && $postsChildren.length < posts.length) {
var lastVisiblePostID = $postsChildren[$postsChildren.length-1].id;
lastVisiblePostID = lastVisiblePostID.substr(lastVisiblePostID.indexOf('-')+1);
for (var i=0; i<posts.length-1; i++) {
if (posts[i].id == lastVisiblePostID) {
var $moreBtn = document.getElementById('more-posts');
if ($moreBtn) {
// Should always land here (?)
$posts.el.insertBefore(createPostEl(posts[i-1]), $moreBtn);
} else {
$posts.el.appendChild(createPostEl(posts[i-1]));
}
}
}
}
});
} else {
alert('Something went seriously wrong. Try refreshing.');
}
}
}
var getFormattedDate = function(d) {
var mos = [
"January", "February", "March",
"April", "May", "June", "July",
"August", "September", "October",
"November", "December"
];
var day = d.getDate();
var mo = d.getMonth();
var yr = d.getFullYear();
return mos[mo] + ' ' + day + ', ' + yr;
};
var posts = JSON.parse(H.get('posts', '[]'));
var initialListPop = function() {
pages = Math.ceil(posts.length / postsPerPage);
loadPage(page, true);
};
var $posts = H.getEl("posts");
if ($posts.el == null) {
$posts = H.getEl("unsynced-posts");
}
$posts.el.innerHTML = '<p class="status">Reading...</p>';
var createMorePostsEl = function() {
var $more = document.createElement('div');
var nextPage = page+1;
$more.id = 'more-posts';
$more.innerHTML = '<p><a href="#' + nextPage + '">More...</a></p>';
return $more;
};
var localPosts = function() {
var $delPost, lastDelPost, lastInfoHTML;
var $info = He.get('unsynced-posts-info');
var findPostIdx = function(id) {
for (var i=0; i<posts.length; i++) {
if (posts[i].id == id) {
return i;
}
}
return -1;
};
var DismissError = function(e, el) {
e.preventDefault();
var $errorMsg = el.parentNode.previousElementSibling;
$errorMsg.parentNode.removeChild($errorMsg);
var $errorMsgNav = el.parentNode;
$errorMsgNav.parentNode.removeChild($errorMsgNav);
};
var DeletePostLocal = function(e, el, id) {
e.preventDefault();
if (!window.confirm('Are you sure you want to delete this post?')) {
return;
}
var i = findPostIdx(id);
if (i > -1) {
lastDelPost = posts.splice(i, 1)[0];
$delPost = H.getEl('post-'+id);
$delPost.setClass('del-undo');
var $unsyncPosts = document.getElementById('unsynced-posts');
var visible = $unsyncPosts.children.length;
for (var i=0; i < $unsyncPosts.children.length; i++) { // NOTE: *.children support in IE9+
if ($unsyncPosts.children[i].className.indexOf('del-undo') !== -1) {
visible--;
}
}
if (visible == 0) {
H.getEl('unsynced-posts-header').hide();
// TODO: fix undo functionality and don't do the following:
H.getEl('unsynced-posts-info').hide();
}
H.set('posts', JSON.stringify(posts));
// TODO: fix undo functionality and re-add
//lastInfoHTML = $info.innerHTML;
//$info.innerHTML = 'Unsynced entry deleted. <a href="#" onclick="localPosts.undoDelete()">Undo</a>.';
}
};
var UndoDelete = function() {
// TODO: fix this header reappearing
H.getEl('unsynced-posts-header').show();
$delPost.removeClass('del-undo');
$info.innerHTML = lastInfoHTML;
};
return {
dismissError: DismissError,
deletePost: DeletePostLocal,
undoDelete: UndoDelete,
};
}();
+var movePostHTML = function(postID) {
+ let $tmpl = document.getElementById('move-tmpl');
+ if ($tmpl === null) {
+ return "";
+ }
+ return $tmpl.innerHTML.replace(/POST_ID/g, postID);
+}
var createPostEl = function(post, owned) {
var $post = document.createElement('div');
var title = (post.title || post.id);
title = title.replace(/</g, "&lt;");
$post.id = 'post-' + post.id;
$post.className = 'post';
$post.innerHTML = '<h3><a href="/' + post.id + '">' + title + '</a></h3>';
var posted = "";
if (post.created) {
posted = getFormattedDate(new Date(post.created))
}
var hasDraft = H.exists('draft' + post.id);
- $post.innerHTML += '<h4><date>' + posted + '</date> <a class="action" href="/pad/' + post.id + '">edit' + (hasDraft ? 'ed' : '') + '</a> <a class="delete action" href="/' + post.id + '" onclick="delPost(event, \'' + post.id + '\'' + (owned === true ? ', true' : '') + ')">delete</a></h4>';
+ $post.innerHTML += '<h4><date>' + posted + '</date> <a class="action" href="/pad/' + post.id + '">edit' + (hasDraft ? 'ed' : '') + '</a> <a class="delete action" href="/' + post.id + '" onclick="delPost(event, \'' + post.id + '\'' + (owned === true ? ', true' : '') + ')">delete</a> '+movePostHTML(post.id)+'</h4>';
if (post.error) {
$post.innerHTML += '<p class="error"><strong>Sync error:</strong> ' + post.error + ' <nav><a href="#" onclick="localPosts.dismissError(event, this)">dismiss</a> <a href="#" onclick="localPosts.deletePost(event, this, \''+post.id+'\')">remove post</a></nav></p>';
}
if (post.summary) {
$post.innerHTML += '<p>' + post.summary.replace(/</g, "&lt;") + '</p>';
} else if (post.body) {
var preview;
if (post.body.length > 140) {
preview = post.body.substr(0, 140) + '...';
} else {
preview = post.body;
}
$post.innerHTML += '<p>' + preview.replace(/</g, "&lt;") + '</p>';
}
return $post;
};
var loadPage = function(p, loadAll) {
if (loadAll) {
$posts.el.innerHTML = '';
}
var startPost = posts.length - 1 - (loadAll ? 0 : ((p-1)*postsPerPage));
var endPost = posts.length - 1 - (p*postsPerPage);
for (var i=startPost; i>=0 && i>endPost; i--) {
$posts.el.appendChild(createPostEl(posts[i]));
}
if (loadAll) {
if (p < pages) {
$posts.el.appendChild(createMorePostsEl());
}
} else {
var $moreEl = document.getElementById('more-posts');
$moreEl.parentNode.removeChild($moreEl);
}
try {
postsLoaded(posts.length);
} catch (e) {}
};
var getPageNum = function(url) {
var hash;
if (url) {
hash = url.substr(url.indexOf('#')+1);
} else {
hash = window.location.hash.substr(1);
}
var page = hash || 1;
page = parseInt(page);
if (isNaN(page)) {
page = 1;
}
return page;
};
var postsPerPage = 10;
var pages = 0;
var page = getPageNum();
window.addEventListener('hashchange', function(e) {
var newPage = getPageNum();
var didPageIncrement = newPage == getPageNum(e.oldURL) + 1;
loadPage(newPage, !didPageIncrement);
});
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 + (typeof token !== 'undefined' ? "?token=" + encodeURIComponent(token) : '');
http.open("DELETE", url, true);
http.onreadystatechange = function() {
if (http.readyState == 4) {
deleting = false;
if (http.status == 204 || http.status == 404) {
for (var i=0; i<posts.length; i++) {
if (posts[i].id == postID) {
// TODO: use this return value, along will full content, for restoring post
posts.splice(i, 1);
break;
}
}
H.set('posts', JSON.stringify(posts));
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. Please try again.");
}
}
}
http.send();
};
var hasWritten = H.get('lastDoc', '') !== '';
var displayNoPosts = function() {
if (auth) {
$posts.el.innerHTML = '';
return;
}
var cta = '<a href="/pad">Create a post</a> and it\'ll appear here.';
if (hasWritten) {
cta = '<a href="/pad">Finish your post</a> and it\'ll appear here.';
}
H.getEl("posts").el.innerHTML = '<p class="status">No posts created yet.</p><p class="status">' + cta + '</p>';
};
if (posts.length == 0) {
displayNoPosts();
} else {
initialListPop();
}
diff --git a/templates/user/articles.tmpl b/templates/user/articles.tmpl
index 0eccc9e..92f9c40 100644
--- a/templates/user/articles.tmpl
+++ b/templates/user/articles.tmpl
@@ -1,205 +1,227 @@
{{define "articles"}}
{{template "header" .}}
<style type="text/css">
a.loading {
font-style: italic;
color: #666;
}
+ #move-tmpl {
+ display: none;
+ }
</style>
<div class="snug content-container">
{{if .Flashes}}<ul class="errors">
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
</ul>{{end}}
{{if .Silenced}}
{{template "user-silenced"}}
{{end}}
<h1 id="posts-header">Drafts</h1>
{{ if .AnonymousPosts }}
<p>These are your draft posts. You can share them individually (without a blog) or move them to your blog when you're ready.</p>
<div id="anon-posts" class="atoms posts">
{{ range $el := .AnonymousPosts }}<div id="post-{{.ID}}" class="post">
<h3><a href="/{{if $.SingleUser}}d/{{end}}{{.ID}}" itemprop="url">{{.DisplayTitle}}</a></h3>
<h4>
<date datetime="{{.Created}}" pubdate itemprop="datePublished" content="{{.Created}}">{{.DisplayDate}}</date>
<a class="action" href="/{{if $.SingleUser}}d/{{end}}{{.ID}}/edit">edit</a>
<a class="delete action" href="/{{.ID}}" onclick="delPost(event, '{{.ID}}', true)">delete</a>
{{ if $.Collections }}
{{if gt (len $.Collections) 1}}<div class="action flat-select">
<select id="move-{{.ID}}" onchange="postActions.multiMove(this, '{{.ID}}', {{if $.SingleUser}}true{{else}}false{{end}})" title="Move this post to one of your blogs">
<option style="display:none"></option>
{{range $.Collections}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}
</select>
<label for="move-{{.ID}}">move to...</label>
<img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
</div>{{else}}
{{range $.Collections}}
<a class="action" href="/{{$el.ID}}" title="Publish this post to your blog '{{.DisplayTitle}}'" onclick="postActions.move(this, '{{$el.ID}}', '{{.Alias}}', {{if $.SingleUser}}true{{else}}false{{end}});return false">move to {{.DisplayTitle}}</a>
{{end}}
{{end}}
{{ end }}
</h4>
{{if .Summary}}<p>{{.SummaryHTML}}</p>{{end}}
</div>{{end}}
</div>
{{if eq (len .AnonymousPosts) 10}}<p id="load-more-p"><a href="#load">Load more...</a></p>{{end}}
{{ else }}<div id="no-posts-published">
<p>Your anonymous and draft posts will show up here once you've published some. You'll be able to share them individually (without a blog) or move them to a blog when you're ready.</p>
{{if not .SingleUser}}<p>Alternatively, see your blogs and their posts on your <a href="/me/c/">Blogs</a> page.</p>{{end}}
<p class="text-cta"><a href="{{if .SingleUser}}/me/new{{else}}/{{end}}">Start writing</a></p></div>{{ end }}
<div id="moving"></div>
<h2 id="unsynced-posts-header" style="display: none">unsynced posts</h2>
<div id="unsynced-posts-info" style="margin-top: 1em"></div>
<div id="unsynced-posts" class="atoms"></div>
</div>
+{{ if .Collections }}
+ <div id="move-tmpl">
+ {{if gt (len .Collections) 1}}
+ <div class="action flat-select">
+ <select id="move-POST_ID" onchange="postActions.multiMove(this, 'POST_ID', {{if .SingleUser}}true{{else}}false{{end}})" title="Move this post to one of your blogs">
+ <option style="display:none"></option>
+ {{range .Collections}}<option value="{{.Alias}}">{{.DisplayTitle}}</option>{{end}}
+ </select>
+ <label for="move-POST_ID">move to...</label>
+ <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" />
+ </div>
+ {{else}}
+ {{range .Collections}}
+ <a class="action" href="/POST_ID" title="Publish this post to your blog '{{.DisplayTitle}}'" onclick="postActions.move(this, 'POST_ID', '{{.Alias}}', {{if $.SingleUser}}true{{else}}false{{end}});return false">move to {{.DisplayTitle}}</a>
+ {{end}}
+ {{end}}
+ </div>
+{{ end }}
+
<script src="/js/h.js"></script>
<script src="/js/postactions.js"></script>
<script>
var auth = true;
function postsLoaded(n) {
if (n == 0) {
return;
}
document.getElementById('unsynced-posts-header').style.display = 'block';
var syncing = false;
var $pInfo = document.getElementById('unsynced-posts-info');
$pInfo.className = 'alert info';
var plural = n != 1;
$pInfo.innerHTML = '<p>You have <strong>'+n+'</strong> post'+(plural?'s that aren\'t':' that isn\'t')+' synced to your account yet. <a href="#" id="btn-sync">Sync '+(plural?'them':'it')+' now</a>.</p>';
var $noPosts = document.getElementById('no-posts-published');
if ($noPosts != null) {
$noPosts.style.display = 'none';
document.getElementById('posts-header').style.display = 'none';
}
H.getEl('btn-sync').on('click', function(e) {
e.preventDefault();
if (syncing) {
return;
}
var http = new XMLHttpRequest();
var params = [];
var posts = JSON.parse(H.get('posts', '[]'));
if (posts.length > 0) {
for (var i=0; i<posts.length; i++) {
params.push({id: posts[i].id, token: posts[i].token});
}
}
this.style.fontWeight = 'bold';
this.innerText = 'Syncing '+(plural?'them':'it')+' now...';
http.open("POST", "/api/posts/claim", true);
// Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/json");
http.onreadystatechange = function() {
if (http.readyState == 4) {
syncing = false;
this.innerText = 'Importing '+(plural?'them':'it')+' now...';
if (http.status == 200) {
var res = JSON.parse(http.responseText);
if (res.data.length > 0) {
if (res.data.length != posts.length) {
// TODO: handle something that royally fucked up
console.error("Request and result array length didn't match!");
return;
}
for (var i=0; i<res.data.length; i++) {
if (res.data[i].code == 200) {
// Post successfully claimed.
for (var j=0; j<posts.length; j++) {
// Find post in local store
if (posts[j].id == res.data[i].post.id) {
// Remove this post
posts.splice(j, 1);
break;
}
}
} else {
for (var j=0; j<posts.length; j++) {
// Find post in local store
if (posts[j].id == res.data[i].id) {
// Note the error in the local post
posts[j].error = res.data[i].error_msg;
break;
}
}
}
}
H.set('posts', JSON.stringify(posts));
location.reload();
}
} else {
// TODO: handle error visually (option to retry)
console.error("Didn't work at all, man.");
this.style.fontWeight = 'normal';
this.innerText = 'Sync '+(plural?'them':'it')+' now';
}
}
}
http.send(JSON.stringify(params));
syncing = true;
});
}
var $loadMore = H.getEl("load-more-p");
var curPage = 1;
var isLoadingMore = false;
function loadMorePosts() {
if (isLoadingMore === true) {
return;
}
var $link = this;
isLoadingMore = true;
$link.className = 'loading';
$link.textContent = 'Loading posts...';
var $posts = H.getEl("anon-posts");
curPage++;
var http = new XMLHttpRequest();
var url = "/api/me/posts?anonymous=1&page=" + curPage;
http.open("GET", url, true);
http.setRequestHeader("Content-type", "application/json");
http.onreadystatechange = function() {
if (http.readyState == 4) {
if (http.status == 200) {
var data = JSON.parse(http.responseText);
for (var i=0; i<data.data.length; i++) {
$posts.el.appendChild(createPostEl(data.data[i], true));
}
if (data.data.length < 10) {
$loadMore.el.parentNode.removeChild($loadMore.el);
}
} else {
alert("Failed to load more posts. Please try again.");
curPage--;
}
isLoadingMore = false;
$link.className = '';
$link.textContent = 'Load more...';
}
}
http.send();
}
$loadMore.el.querySelector('a').addEventListener('click', loadMorePosts);
</script>
<script src="/js/posts.js"></script>
{{template "footer" .}}
{{end}}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 6, 9:59 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3168287

Event Timeline