Support loading more draft posts

This adds a "load more" button to the bottom of the draft posts page,
which calls /api/me/posts with new parameters and the current page
number. It then populates the page accordingly.

Ref T696 - load anon. posts with ?anonymous=1&page=1
Ref T401 - completes UI for post loading
pull/356/head
Matt Baer 4 years ago
parent 7eeba4dc9e
commit 09e70e07f8
  1. 17
      account.go
  2. 20
      handle.go
  3. 2
      routes.go
  4. 12
      static/js/posts.js
  5. 57
      templates/user/articles.tmpl

@ -16,6 +16,7 @@ import (
"html/template" "html/template"
"net/http" "net/http"
"regexp" "regexp"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -691,6 +692,22 @@ func viewMyPostsAPI(app *App, u *User, w http.ResponseWriter, r *http.Request) e
return ErrBadRequestedType return ErrBadRequestedType
} }
isAnonPosts := r.FormValue("anonymous") == "1"
if isAnonPosts {
pageStr := r.FormValue("page")
pg, err := strconv.Atoi(pageStr)
if err != nil {
log.Error("Error parsing page parameter '%s': %s", pageStr, err)
pg = 1
}
p, err := app.db.GetAnonymousPosts(u, pg)
if err != nil {
return err
}
return impart.WriteSuccess(w, p, http.StatusOK)
}
var err error var err error
p := GetPostsCache(u.ID) p := GetPostsCache(u.ID)
if p == nil { if p == nil {

@ -287,6 +287,26 @@ func (h *Handler) UserAPI(f userHandlerFunc) http.HandlerFunc {
return h.UserAll(false, f, apiAuth) return h.UserAll(false, f, apiAuth)
} }
// UserWebAPI handles endpoints that accept a user authorized either via the web (cookies) or an Authorization header.
func (h *Handler) UserWebAPI(f userHandlerFunc) http.HandlerFunc {
return h.UserAll(false, f, func(app *App, r *http.Request) (*User, error) {
// Authorize user via cookies
u := getUserSession(app, r)
if u != nil {
return u, nil
}
// Fall back to access token, since user isn't logged in via web
var err error
u, err = apiAuth(app, r)
if err != nil {
return nil, err
}
return u, nil
})
}
func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerFunc { func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
handleFunc := func() error { handleFunc := func() error {

@ -109,7 +109,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
write.HandleFunc("/api/me", handler.All(viewMeAPI)).Methods("GET") write.HandleFunc("/api/me", handler.All(viewMeAPI)).Methods("GET")
apiMe := write.PathPrefix("/api/me/").Subrouter() apiMe := write.PathPrefix("/api/me/").Subrouter()
apiMe.HandleFunc("/", handler.All(viewMeAPI)).Methods("GET") apiMe.HandleFunc("/", handler.All(viewMeAPI)).Methods("GET")
apiMe.HandleFunc("/posts", handler.UserAPI(viewMyPostsAPI)).Methods("GET") apiMe.HandleFunc("/posts", handler.UserWebAPI(viewMyPostsAPI)).Methods("GET")
apiMe.HandleFunc("/collections", handler.UserAPI(viewMyCollectionsAPI)).Methods("GET") apiMe.HandleFunc("/collections", handler.UserAPI(viewMyCollectionsAPI)).Methods("GET")
apiMe.HandleFunc("/password", handler.All(updatePassphrase)).Methods("POST") apiMe.HandleFunc("/password", handler.All(updatePassphrase)).Methods("POST")
apiMe.HandleFunc("/self", handler.All(updateSettings)).Methods("POST") apiMe.HandleFunc("/self", handler.All(updateSettings)).Methods("POST")

@ -181,7 +181,7 @@ var localPosts = function() {
undoDelete: UndoDelete, undoDelete: UndoDelete,
}; };
}(); }();
var createPostEl = function(post) { var createPostEl = function(post, owned) {
var $post = document.createElement('div'); var $post = document.createElement('div');
var title = (post.title || post.id); var title = (post.title || post.id);
title = title.replace(/</g, "&lt;"); title = title.replace(/</g, "&lt;");
@ -194,13 +194,21 @@ var createPostEl = function(post) {
posted = getFormattedDate(new Date(post.created)) posted = getFormattedDate(new Date(post.created))
} }
var hasDraft = H.exists('draft' + post.id); 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>'; $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>';
if (post.error) { 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>'; $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) { if (post.summary) {
$post.innerHTML += '<p>' + post.summary.replace(/</g, "&lt;") + '</p>'; $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; return $post;
}; };

@ -1,5 +1,11 @@
{{define "articles"}} {{define "articles"}}
{{template "header" .}} {{template "header" .}}
<style type="text/css">
a.loading {
font-style: italic;
color: #666;
}
</style>
<div class="snug content-container"> <div class="snug content-container">
@ -15,7 +21,7 @@
{{ if .AnonymousPosts }} {{ 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> <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 class="atoms posts"> <div id="anon-posts" class="atoms posts">
{{ range $el := .AnonymousPosts }}<div id="post-{{.ID}}" class="post"> {{ range $el := .AnonymousPosts }}<div id="post-{{.ID}}" class="post">
<h3><a href="/{{if $.SingleUser}}d/{{end}}{{.ID}}" itemprop="url">{{.DisplayTitle}}</a></h3> <h3><a href="/{{if $.SingleUser}}d/{{end}}{{.ID}}" itemprop="url">{{.DisplayTitle}}</a></h3>
<h4> <h4>
@ -39,9 +45,12 @@
</h4> </h4>
{{if .Summary}}<p>{{.SummaryHTML}}</p>{{end}} {{if .Summary}}<p>{{.SummaryHTML}}</p>{{end}}
</div>{{end}} </div>{{end}}
</div>{{ else }}<div id="no-posts-published"> </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> <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}} {{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 }} <p class="text-cta"><a href="{{if .SingleUser}}/me/new{{else}}/{{end}}">Start writing</a></p></div>{{ end }}
<div id="moving"></div> <div id="moving"></div>
@ -145,6 +154,50 @@ function postsLoaded(n) {
syncing = true; 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>
<script src="/js/posts.js"></script> <script src="/js/posts.js"></script>

Loading…
Cancel
Save