diff --git a/app/assets/javascripts/discourse/controllers/user-activity-bookmarks-with-reminders.js b/app/assets/javascripts/discourse/controllers/user-activity-bookmarks-with-reminders.js index ad8ab35a6b5..11cc0bb6225 100644 --- a/app/assets/javascripts/discourse/controllers/user-activity-bookmarks-with-reminders.js +++ b/app/assets/javascripts/discourse/controllers/user-activity-bookmarks-with-reminders.js @@ -1,4 +1,5 @@ import Controller from "@ember/controller"; +import { Promise } from "rsvp"; import { inject } from "@ember/controller"; import discourseComputed from "discourse-common/utils/decorators"; import Bookmark from "discourse/models/bookmark"; @@ -21,17 +22,7 @@ export default Controller.extend({ return this.model .loadItems() .then(response => { - if (response && response.no_results_help) { - this.set("noResultsHelp", response.no_results_help); - } - - if (response && response.bookmarks) { - let bookmarks = []; - response.bookmarks.forEach(bookmark => { - bookmarks.push(Bookmark.create(bookmark)); - }); - this.content.pushObjects(bookmarks); - } + this.processLoadResponse(response); }) .catch(() => { this.set("noResultsHelp", I18n.t("bookmarks.list_permission_denied")); @@ -49,9 +40,46 @@ export default Controller.extend({ return loaded && contentLength === 0; }, + processLoadResponse(response) { + response = response.user_bookmark_list; + + if (response && response.no_results_help) { + this.set("noResultsHelp", response.no_results_help); + } + + this.model.more_bookmarks_url = response.more_bookmarks_url; + + if (response && response.bookmarks) { + let bookmarks = []; + response.bookmarks.forEach(bookmark => { + bookmarks.push(Bookmark.create(bookmark)); + }); + this.content.pushObjects(bookmarks); + } + }, + actions: { removeBookmark(bookmark) { return bookmark.destroy().then(() => this.loadItems()); + }, + + loadMore() { + if (this.loadingMore) { + return Promise.resolve(); + } + this.set("loadingMore", true); + + return this.model + .loadMore() + .then(response => this.processLoadResponse(response)) + .catch(() => { + this.set("noResultsHelp", I18n.t("bookmarks.list_permission_denied")); + }) + .finally(() => + this.setProperties({ + loadingMore: false + }) + ); } } }); diff --git a/app/assets/javascripts/discourse/models/bookmark.js b/app/assets/javascripts/discourse/models/bookmark.js index 1997ab632c4..8a8c09f35f1 100644 --- a/app/assets/javascripts/discourse/models/bookmark.js +++ b/app/assets/javascripts/discourse/models/bookmark.js @@ -117,6 +117,22 @@ const Bookmark = RestModel.extend({ loadItems() { return ajax(`/u/${this.user.username}/bookmarks.json`, { cache: "false" }); + }, + + loadMore() { + if (!this.more_bookmarks_url) { + return Promise.resolve(); + } + + let moreUrl = this.more_bookmarks_url; + if (moreUrl) { + let [url, params] = moreUrl.split("?"); + moreUrl = url; + if (params) { + moreUrl += "?" + params; + } + } + return ajax({ url: moreUrl }); } }); diff --git a/app/assets/javascripts/discourse/templates/user/bookmarks.hbs b/app/assets/javascripts/discourse/templates/user/bookmarks.hbs index 228490bfa4f..92c9979b544 100644 --- a/app/assets/javascripts/discourse/templates/user/bookmarks.hbs +++ b/app/assets/javascripts/discourse/templates/user/bookmarks.hbs @@ -2,50 +2,53 @@
{{noResultsHelp}}
{{else}} {{#conditional-loading-spinner condition=loading}} - - - - - - - - - {{#each content as |bookmark|}} - - + + {{raw "list/activity-column" topic=bookmark class="num" tagName="td"}} + + + {{/each}} + +
{{i18n "topic.title"}}{{i18n "post.bookmarks.created"}}{{i18n "activity"}} 
{{format-date bookmark.created_at format="tiny"}} + {{bookmark-actions-dropdown bookmark=bookmark removeBookmark=(action "removeBookmark")}} +
+ {{conditional-loading-spinner condition=loadingMore}} + {{/load-more}} {{/conditional-loading-spinner}} {{/if}} diff --git a/app/assets/javascripts/discourse/widgets/quick-access-bookmarks.js b/app/assets/javascripts/discourse/widgets/quick-access-bookmarks.js index b63c2b527b6..7bbae631984 100644 --- a/app/assets/javascripts/discourse/widgets/quick-access-bookmarks.js +++ b/app/assets/javascripts/discourse/widgets/quick-access-bookmarks.js @@ -62,6 +62,8 @@ createWidgetFrom(QuickAccessPanel, "quick-access-bookmarks", { limit: this.estimateItemLimit() } }).then(result => { + result = result.user_bookmark_list; + // The empty state help text for bookmarks page is localized on the // server. if (result.no_results_help) { diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 15e36dd9d19..81eeddab05c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1404,15 +1404,18 @@ class UsersController < ApplicationController respond_to do |format| format.json do - bookmarks = BookmarkQuery.new(user: user, guardian: guardian, params: params).list_all + bookmark_list = UserBookmarkList.new(user: user, guardian: guardian, params: params) + bookmark_list.load - if bookmarks.empty? + if bookmark_list.bookmarks.empty? render json: { bookmarks: [], no_results_help: I18n.t("user_activity.no_bookmarks.self") } else - render_serialized(bookmarks, UserBookmarkSerializer, root: 'bookmarks') + page = params[:page].to_i + 1 + bookmark_list.more_bookmarks_url = "#{Discourse.base_path}/u/#{params[:username]}/bookmarks.json?page=#{page}" + render_serialized(bookmark_list, UserBookmarkListSerializer) end end format.ics do diff --git a/app/models/user.rb b/app/models/user.rb index 284f15c7883..4ae0f86cc08 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -14,6 +14,7 @@ class User < ActiveRecord::Base has_many :tag_users, dependent: :destroy has_many :user_api_keys, dependent: :destroy has_many :topics + has_many :bookmarks # dependent deleting handled via before_destroy has_many :user_actions diff --git a/app/models/user_bookmark_list.rb b/app/models/user_bookmark_list.rb new file mode 100644 index 00000000000..3c5a00280fd --- /dev/null +++ b/app/models/user_bookmark_list.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class UserBookmarkList + include ActiveModel::Serialization + + PER_PAGE = 20 + + attr_reader :bookmarks + attr_accessor :more_bookmarks_url + + def initialize(user: user, guardian: guardian, params: params) + @user = user + @guardian = guardian + @params = params.merge(per_page: PER_PAGE) + @bookmarks = [] + end + + def load + @bookmarks = BookmarkQuery.new(user: @user, guardian: @guardian, params: @params).list_all + @bookmarks + end + + def per_page + @per_page ||= PER_PAGE + end +end diff --git a/app/serializers/user_bookmark_list_serializer.rb b/app/serializers/user_bookmark_list_serializer.rb new file mode 100644 index 00000000000..4e82e44b177 --- /dev/null +++ b/app/serializers/user_bookmark_list_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class UserBookmarkListSerializer < ApplicationSerializer + attributes :more_bookmarks_url + + has_many :bookmarks, serializer: UserBookmarkSerializer, embed: :objects + + def include_more_bookmarks_url? + object.bookmarks.size == object.per_page + end +end diff --git a/lib/bookmark_query.rb b/lib/bookmark_query.rb index b55e8eb8b6a..45442c211f4 100644 --- a/lib/bookmark_query.rb +++ b/lib/bookmark_query.rb @@ -22,6 +22,8 @@ class BookmarkQuery @user = user @params = params @guardian = guardian || Guardian.new(@user) + @page = @params[:page].to_i + @limit = @params[:limit].present? ? @params[:limit].to_i : @params[:per_page] end def list_all @@ -34,10 +36,12 @@ class BookmarkQuery results = results.merge(Post.secured(@guardian)) - if @params[:limit] - results = results.limit(@params[:limit]) + if @page.positive? + results = results.offset(@page * @params[:per_page]) end + results = results.limit(@limit) + if BookmarkQuery.preloaded_custom_fields.any? Topic.preload_custom_fields( results.map(&:topic), BookmarkQuery.preloaded_custom_fields diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 561eaeb12d9..59f705f8ebf 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -4198,7 +4198,7 @@ describe UsersController do sign_in(user) get "/u/#{user.username}/bookmarks.json" expect(response.status).to eq(200) - expect(JSON.parse(response.body)['bookmarks'].map { |b| b['id'] }).to match_array([bookmark1.id, bookmark2.id]) + expect(JSON.parse(response.body)['user_bookmark_list']['bookmarks'].map { |b| b['id'] }).to match_array([bookmark1.id, bookmark2.id]) end it "does not show another user's bookmarks" do