FEATURE: Add lazy loading to user bookmarks list (#9317)

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>&nbsp;</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>&nbsp;</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

This commit appears in #9317 which was merged by martin.