From 7eeba4dc9e3b3324d8ec57fd0eea97bee100cb4a Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 30 Jul 2020 16:28:21 -0400 Subject: [PATCH 1/4] Limit initial draft post loading to 10 posts Ref T401 --- account.go | 2 +- database.go | 17 ++++++++++++++--- export.go | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/account.go b/account.go index 56a4f84..aead6c5 100644 --- a/account.go +++ b/account.go @@ -731,7 +731,7 @@ func viewMyCollectionsAPI(app *App, u *User, w http.ResponseWriter, r *http.Requ } func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) error { - p, err := app.db.GetAnonymousPosts(u) + p, err := app.db.GetAnonymousPosts(u, 1) if err != nil { log.Error("unable to fetch anon posts: %v", err) } diff --git a/database.go b/database.go index 000d62c..07663ad 100644 --- a/database.go +++ b/database.go @@ -77,7 +77,7 @@ type writestore interface { GetTotalCollections() (int64, error) GetTotalPosts() (int64, error) GetTopPosts(u *User, alias string) (*[]PublicPost, error) - GetAnonymousPosts(u *User) (*[]PublicPost, error) + GetAnonymousPosts(u *User, page int) (*[]PublicPost, error) GetUserPosts(u *User) (*[]PublicPost, error) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias, hostName string) (*PublicPost, error) @@ -1774,8 +1774,19 @@ func (db *datastore) GetTopPosts(u *User, alias string) (*[]PublicPost, error) { return &posts, nil } -func (db *datastore) GetAnonymousPosts(u *User) (*[]PublicPost, error) { - rows, err := db.Query("SELECT id, view_count, title, created, updated, content FROM posts WHERE owner_id = ? AND collection_id IS NULL ORDER BY created DESC", u.ID) +func (db *datastore) GetAnonymousPosts(u *User, page int) (*[]PublicPost, error) { + pagePosts := 10 + start := page*pagePosts - pagePosts + if page == 0 { + start = 0 + pagePosts = 1000 + } + + limitStr := "" + if page > 0 { + limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts) + } + rows, err := db.Query("SELECT id, view_count, title, created, updated, content FROM posts WHERE owner_id = ? AND collection_id IS NULL ORDER BY created DESC"+limitStr, u.ID) if err != nil { log.Error("Failed selecting from posts: %v", err) return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user anonymous posts."} diff --git a/export.go b/export.go index 592bc0c..bdfd7c4 100644 --- a/export.go +++ b/export.go @@ -110,7 +110,7 @@ func compileFullExport(app *App, u *User) *ExportUser { log.Error("unable to fetch collections: %v", err) } - posts, err := app.db.GetAnonymousPosts(u) + posts, err := app.db.GetAnonymousPosts(u, 0) if err != nil { log.Error("unable to fetch anon posts: %v", err) } From 09e70e07f8f24e2408d553335d8f53059ae5eae9 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 30 Jul 2020 16:46:01 -0400 Subject: [PATCH 2/4] 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 --- account.go | 17 +++++++++++ handle.go | 20 +++++++++++++ routes.go | 2 +- static/js/posts.js | 12 ++++++-- templates/user/articles.tmpl | 57 ++++++++++++++++++++++++++++++++++-- 5 files changed, 103 insertions(+), 5 deletions(-) diff --git a/account.go b/account.go index aead6c5..1557820 100644 --- a/account.go +++ b/account.go @@ -16,6 +16,7 @@ import ( "html/template" "net/http" "regexp" + "strconv" "strings" "sync" "time" @@ -691,6 +692,22 @@ func viewMyPostsAPI(app *App, u *User, w http.ResponseWriter, r *http.Request) e 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 p := GetPostsCache(u.ID) if p == nil { diff --git a/handle.go b/handle.go index 1b5470f..1424c59 100644 --- a/handle.go +++ b/handle.go @@ -287,6 +287,26 @@ func (h *Handler) UserAPI(f userHandlerFunc) http.HandlerFunc { 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 { return func(w http.ResponseWriter, r *http.Request) { handleFunc := func() error { diff --git a/routes.go b/routes.go index b34bd3d..bebe383 100644 --- a/routes.go +++ b/routes.go @@ -109,7 +109,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { write.HandleFunc("/api/me", handler.All(viewMeAPI)).Methods("GET") apiMe := write.PathPrefix("/api/me/").Subrouter() 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("/password", handler.All(updatePassphrase)).Methods("POST") apiMe.HandleFunc("/self", handler.All(updateSettings)).Methods("POST") diff --git a/static/js/posts.js b/static/js/posts.js index 58b55a2..ef3143d 100644 --- a/static/js/posts.js +++ b/static/js/posts.js @@ -181,7 +181,7 @@ var localPosts = function() { undoDelete: UndoDelete, }; }(); -var createPostEl = function(post) { +var createPostEl = function(post, owned) { var $post = document.createElement('div'); var title = (post.title || post.id); title = title.replace(/ edit' + (hasDraft ? 'ed' : '') + ' delete'; + $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete

'; if (post.error) { $post.innerHTML += '

Sync error: ' + post.error + '

'; } if (post.summary) { $post.innerHTML += '

' + post.summary.replace(/'; + } else if (post.body) { + var preview; + if (post.body.length > 140) { + preview = post.body.substr(0, 140) + '...'; + } else { + preview = post.body; + } + $post.innerHTML += '

' + preview.replace(/'; } return $post; }; diff --git a/templates/user/articles.tmpl b/templates/user/articles.tmpl index e96d51e..0eccc9e 100644 --- a/templates/user/articles.tmpl +++ b/templates/user/articles.tmpl @@ -1,5 +1,11 @@ {{define "articles"}} {{template "header" .}} +

@@ -15,7 +21,7 @@ {{ if .AnonymousPosts }}

These are your draft posts. You can share them individually (without a blog) or move them to your blog when you're ready.

-
+
{{ range $el := .AnonymousPosts }}

@@ -39,9 +45,12 @@

{{if .Summary}}

{{.SummaryHTML}}

{{end}}
{{end}} -
{{ else }}
+
+{{if eq (len .AnonymousPosts) 10}}

Load more...

{{end}} +{{ else }}

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.

{{if not .SingleUser}}

Alternatively, see your blogs and their posts on your Blogs page.

{{end}} +

Start writing

{{ end }}
@@ -145,6 +154,50 @@ function postsLoaded(n) { 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 From 63fa8d299a9835c787f25a25c94ed610ce705b52 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Wed, 7 Apr 2021 16:44:18 -0400 Subject: [PATCH 3/4] Include 'move to...' action in loaded draft posts Ref T401 --- static/js/posts.js | 9 ++++++++- templates/user/articles.tmpl | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) 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 @@ -181,6 +181,13 @@ var localPosts = function() { 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); @@ -194,7 +201,7 @@ var createPostEl = function(post, owned) { posted = getFormattedDate(new Date(post.created)) } var hasDraft = H.exists('draft' + post.id); - $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete

'; + $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete '+movePostHTML(post.id)+'

'; if (post.error) { $post.innerHTML += '

Sync error: ' + post.error + '

'; 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 @@ -5,6 +5,9 @@ font-style: italic; color: #666; } + #move-tmpl { + display: none; + }
@@ -61,6 +64,25 @@
+{{ if .Collections }} +
+ {{if gt (len .Collections) 1}} +
+ + + +
+ {{else}} + {{range .Collections}} + move to {{.DisplayTitle}} + {{end}} + {{end}} +
+{{ end }} +