Add user management pages
This commit is contained in:
18
static/js/ace.js
Normal file
18
static/js/ace.js
Normal file
File diff suppressed because one or more lines are too long
9
static/js/mode-css.js
Normal file
9
static/js/mode-css.js
Normal file
File diff suppressed because one or more lines are too long
105
static/js/postactions.js
Normal file
105
static/js/postactions.js
Normal file
@@ -0,0 +1,105 @@
|
||||
var postActions = function() {
|
||||
var $container = He.get('moving');
|
||||
var MultiMove = function(el, id) {
|
||||
var lbl = el.options[el.selectedIndex].textContent;
|
||||
var collAlias = el.options[el.selectedIndex].value;
|
||||
var $lbl = He.$('label[for=move-'+id+']')[0];
|
||||
$lbl.textContent = "moving to "+lbl+"...";
|
||||
var params;
|
||||
if (collAlias == '|anonymous|') {
|
||||
params = [id];
|
||||
} else {
|
||||
params = [{
|
||||
id: id
|
||||
}];
|
||||
}
|
||||
var callback = function(code, resp) {
|
||||
if (code == 200) {
|
||||
for (var i=0; i<resp.data.length; i++) {
|
||||
if (resp.data[i].code == 200) {
|
||||
$lbl.innerHTML = "moved to <strong>"+lbl+"</strong>";
|
||||
var newPostURL = "/"+collAlias+"/"+resp.data[i].post.slug;
|
||||
try {
|
||||
// Posts page
|
||||
He.$('#post-'+resp.data[i].post.id+' > h3 > a')[0].href = newPostURL;
|
||||
} catch (e) {
|
||||
// Blog index
|
||||
var $article = He.get('post-'+resp.data[i].post.id);
|
||||
$article.className = 'norm moved';
|
||||
if (collAlias == '|anonymous|') {
|
||||
$article.innerHTML = '<p><a href="/'+resp.data[i].post.id+'">Unpublished post</a>.</p>';
|
||||
} else {
|
||||
$article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$lbl.innerHTML = "unable to move: "+resp.data[i].error_msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if (collAlias == '|anonymous|') {
|
||||
He.postJSON("/api/posts/disperse", params, callback);
|
||||
} else {
|
||||
He.postJSON("/api/collections/"+collAlias+"/collect", params, callback);
|
||||
}
|
||||
};
|
||||
var Move = function(el, id, collAlias) {
|
||||
var lbl = el.textContent;
|
||||
try {
|
||||
var m = lbl.match(/move to (.*)/);
|
||||
lbl = m[1];
|
||||
} catch (e) {
|
||||
if (collAlias == '|anonymous|') {
|
||||
lbl = "draft";
|
||||
}
|
||||
}
|
||||
|
||||
el.textContent = "moving to "+lbl+"...";
|
||||
if (collAlias == '|anonymous|') {
|
||||
params = [id];
|
||||
} else {
|
||||
params = [{
|
||||
id: id
|
||||
}];
|
||||
}
|
||||
var callback = function(code, resp) {
|
||||
if (code == 200) {
|
||||
for (var i=0; i<resp.data.length; i++) {
|
||||
if (resp.data[i].code == 200) {
|
||||
el.innerHTML = "moved to <strong>"+lbl+"</strong>";
|
||||
el.onclick = null;
|
||||
var newPostURL = "/"+collAlias+"/"+resp.data[i].post.slug;
|
||||
el.href = newPostURL;
|
||||
el.title = "View on "+lbl;
|
||||
try {
|
||||
// Posts page
|
||||
He.$('#post-'+resp.data[i].post.id+' > h3 > a')[0].href = newPostURL;
|
||||
} catch (e) {
|
||||
// Blog index
|
||||
var $article = He.get('post-'+resp.data[i].post.id);
|
||||
$article.className = 'norm moved';
|
||||
if (collAlias == '|anonymous|') {
|
||||
$article.innerHTML = '<p><a href="/'+resp.data[i].post.id+'">Unpublished post</a>.</p>';
|
||||
} else {
|
||||
$article.innerHTML = '<p>Moved to <a style="font-weight:bold" href="'+newPostURL+'">'+lbl+'</a>.</p>';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
el.innerHTML = "unable to move: "+resp.data[i].error_msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collAlias == '|anonymous|') {
|
||||
He.postJSON("/api/posts/disperse", params, callback);
|
||||
} else {
|
||||
He.postJSON("/api/collections/"+collAlias+"/collect", params, callback);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
move: Move,
|
||||
multiMove: MultiMove,
|
||||
};
|
||||
}();
|
315
static/js/posts.js
Normal file
315
static/js/posts.js
Normal file
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* 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 createPostEl = function(post) {
|
||||
var $post = document.createElement('div');
|
||||
var title = (post.title || post.id);
|
||||
title = title.replace(/</g, "<");
|
||||
$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 + '\')">delete</a></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, "<") + '</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();
|
||||
}
|
||||
|
9
static/js/theme-chrome.js
Normal file
9
static/js/theme-chrome.js
Normal file
@@ -0,0 +1,9 @@
|
||||
ace.define("ace/theme/chrome",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-chrome",t.cssText='.ace-chrome .ace_gutter {background: #ebebeb;color: #333;overflow : hidden;}.ace-chrome .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-chrome {background-color: #FFFFFF;color: black;}.ace-chrome .ace_cursor {color: black;}.ace-chrome .ace_invisible {color: rgb(191, 191, 191);}.ace-chrome .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-chrome .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-chrome .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-chrome .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-chrome .ace_fold {}.ace-chrome .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-chrome .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-chrome .ace_support.ace_type,.ace-chrome .ace_support.ace_class.ace-chrome .ace_support.ace_other {color: rgb(109, 121, 222);}.ace-chrome .ace_variable.ace_parameter {font-style:italic;color:#FD971F;}.ace-chrome .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-chrome .ace_comment {color: #236e24;}.ace-chrome .ace_comment.ace_doc {color: #236e24;}.ace-chrome .ace_comment.ace_doc.ace_tag {color: #236e24;}.ace-chrome .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-chrome .ace_variable {color: rgb(49, 132, 149);}.ace-chrome .ace_xml-pe {color: rgb(104, 104, 91);}.ace-chrome .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-chrome .ace_heading {color: rgb(12, 7, 255);}.ace-chrome .ace_list {color:rgb(185, 6, 144);}.ace-chrome .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-chrome .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-chrome .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-chrome .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-chrome .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-chrome .ace_gutter-active-line {background-color : #dcdcdc;}.ace-chrome .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-chrome .ace_storage,.ace-chrome .ace_keyword,.ace-chrome .ace_meta.ace_tag {color: rgb(147, 15, 128);}.ace-chrome .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-chrome .ace_string {color: #1A1AA6;}.ace-chrome .ace_entity.ace_other.ace_attribute-name {color: #994409;}.ace-chrome .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)});
|
||||
(function() {
|
||||
ace.require(["ace/theme/chrome"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
1
static/js/worker-css.js
Normal file
1
static/js/worker-css.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user