diff --git a/account.go b/account.go index 2af9fce..d1e6a21 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 { @@ -731,7 +748,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 88b46e5..df300ce 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) @@ -1806,8 +1806,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) } diff --git a/handle.go b/handle.go index 01d5728..4c454ec 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 2b23bd1..1244e97 100644 --- a/routes.go +++ b/routes.go @@ -115,7 +115,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..dfc30b7 100644 --- a/static/js/posts.js +++ b/static/js/posts.js @@ -181,9 +181,17 @@ var localPosts = function() { undoDelete: UndoDelete, }; }(); -var createPostEl = function(post) { +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); + let p = H.createPost(post.id, "", post.body) + var title = (post.title || p.title || post.id); title = title.replace(/ edit' + (hasDraft ? 'ed' : '') + ' delete'; + $post.innerHTML += '

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

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

Sync error: ' + post.error + '

'; } if (post.summary) { + // TODO: switch to using p.summary, after ensuring it matches summary generated on the backend. $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..92f9c40 100644 --- a/templates/user/articles.tmpl +++ b/templates/user/articles.tmpl @@ -1,5 +1,14 @@ {{define "articles"}} {{template "header" .}} +

@@ -15,7 +24,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 +48,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 }}
@@ -52,6 +64,25 @@
+{{ if .Collections }} +
+ {{if gt (len .Collections) 1}} +
+ + + +
+ {{else}} + {{range .Collections}} + move to {{.DisplayTitle}} + {{end}} + {{end}} +
+{{ end }} +