FEATURE: Add lazy loading to user bookmarks list (#9317)
This is so users with huge amount of bookmarks do not have to wait a long time to see results.
- Add a bookmark list and list serializer to server-side to be able to handle paging and load more URL
- Use load-more component to load more bookmark items, 20 at a time in user activity
- Change the way current user is loaded for bookmark ember models because it was breaking/losing resolvedTimezone when loading more items
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 ad8ab35..11cc0bb 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 1997ab6..8a8c09f 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 228490b..92c9979 100644
--- a/app/assets/javascripts/discourse/templates/user/bookmarks.hbs
+++ b/app/assets/javascripts/discourse/templates/user/bookmarks.hbs
@@ -2,50 +2,53 @@
<div class='alert alert-info'>{{noResultsHelp}}</div>
{{else}}
{{#conditional-loading-spinner condition=loading}}
- <table class="topic-list">
- <thead>
- <th>{{i18n "topic.title"}}</th>
- <th>{{i18n "post.bookmarks.created"}}</th>
- <th>{{i18n "activity"}}</th>
- <th> </th>
- </thead>
- <tbody>
- {{#each content as |bookmark|}}
- <tr class="topic-list-item bookmark-list-item">
- <td class="main-link">
- <span class="link-top-line">
- <div class="bookmark-metadata">
- {{#if bookmark.name}}
- <span class="bookmark-metadata-item">
- {{d-icon "info-circle"}}{{bookmark.name}}
- </span>
- {{/if}}
- {{#if bookmark.reminder_at}}
- <span class="bookmark-metadata-item">
- {{d-icon "far-clock"}}{{bookmark.formattedReminder}}
- </span>
- {{/if}}
- </div>
+ {{#load-more selector=".bookmark-list tr" action=(action "loadMore")}}
+ <table class="topic-list bookmark-list">
+ <thead>
+ <th>{{i18n "topic.title"}}</th>
+ <th>{{i18n "post.bookmarks.created"}}</th>
+ <th>{{i18n "activity"}}</th>
+ <th> </th>
+ </thead>
+ <tbody>
+ {{#each content as |bookmark|}}
+ <tr class="topic-list-item bookmark-list-item">
+ <td class="main-link">
+ <span class="link-top-line">
+ <div class="bookmark-metadata">
+ {{#if bookmark.name}}
+ <span class="bookmark-metadata-item">
+ {{d-icon "info-circle"}}{{bookmark.name}}
+ </span>
+ {{/if}}
+ {{#if bookmark.reminder_at}}
+ <span class="bookmark-metadata-item">
+ {{d-icon "far-clock"}}{{bookmark.formattedReminder}}
+ </span>
+ {{/if}}
+ </div>
- {{topic-status topic=bookmark}}
- {{topic-link bookmark}}
- </span>
- {{#if bookmark.excerpt}}
- <p class="post-excerpt">{{html-safe bookmark.excerpt}}</p>
- {{/if}}
- <div class="link-bottom-line">
- {{category-link bookmark.category}}
- {{discourse-tags bookmark mode="list" tagsForUser=tagsForUser}}
- </div>
- </td>
- <td>{{format-date bookmark.created_at format="tiny"}}</td>
- {{raw "list/activity-column" topic=bookmark class="num" tagName="td"}}
- <td>
- {{bookmark-actions-dropdown bookmark=bookmark removeBookmark=(action "removeBookmark")}}
- </td>
- </tr>
- {{/each}}
- </tbody>
- </table>
+ {{topic-status topic=bookmark}}
+ {{topic-link bookmark}}
+ </span>
+ {{#if bookmark.excerpt}}
+ <p class="post-excerpt">{{html-safe bookmark.excerpt}}</p>
+ {{/if}}
+ <div class="link-bottom-line">
+ {{category-link bookmark.category}}
+ {{discourse-tags bookmark mode="list" tagsForUser=tagsForUser}}
+ </div>
+ </td>
+ <td>{{format-date bookmark.created_at format="tiny"}}</td>
+ {{raw "list/activity-column" topic=bookmark class="num" tagName="td"}}
+ <td>
+ {{bookmark-actions-dropdown bookmark=bookmark removeBookmark=(action "removeBookmark")}}
+ </td>
+ </tr>
+ {{/each}}
+ </tbody>
+ </table>
+ {{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 b63c2b5..7bbae63 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;
+
[... diff too long, it was truncated ...]
GitHub sha: c07dd0d2