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
This commit is contained in:
Martin Brennan
2020-04-01 14:09:07 +10:00
committed by GitHub
parent b8d2261db9
commit c07dd0d22a
10 changed files with 155 additions and 61 deletions
@@ -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
})
);
}
}
});
@@ -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 });
}
});
@@ -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}}
@@ -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) {