diff --git a/app/assets/javascripts/discourse/app/controllers/user-activity-bookmarks.js b/app/assets/javascripts/discourse/app/controllers/user-activity-bookmarks.js
index 48cd32aa864..9a87fafd3dc 100644
--- a/app/assets/javascripts/discourse/app/controllers/user-activity-bookmarks.js
+++ b/app/assets/javascripts/discourse/app/controllers/user-activity-bookmarks.js
@@ -14,6 +14,10 @@ export default Controller.extend({
content: null,
loading: false,
noResultsHelp: null,
+ searchTerm: null,
+ q: null,
+
+ queryParams: ["q"],
loadItems() {
this.setProperties({
@@ -22,8 +26,12 @@ export default Controller.extend({
noResultsHelp: null
});
+ if (this.q && !this.searchTerm) {
+ this.set("searchTerm", this.q);
+ }
+
return this.model
- .loadItems()
+ .loadItems({ q: this.searchTerm })
.then(response => this._processLoadResponse(response))
.catch(() => this._bookmarksListDenied())
.finally(() =>
@@ -43,6 +51,12 @@ export default Controller.extend({
this.content.removeObject(bookmark);
},
+ @action
+ search() {
+ this.set("q", this.searchTerm);
+ this.loadItems();
+ },
+
@action
removeBookmark(bookmark) {
const deleteBookmark = () => {
@@ -86,7 +100,7 @@ export default Controller.extend({
this.set("loadingMore", true);
return this.model
- .loadMore()
+ .loadMore({ q: this.searchTerm })
.then(response => this._processLoadResponse(response))
.catch(() => this._bookmarksListDenied())
.finally(() => this.set("loadingMore", false));
diff --git a/app/assets/javascripts/discourse/app/models/bookmark.js b/app/assets/javascripts/discourse/app/models/bookmark.js
index c7a3249144f..ba5a34777d8 100644
--- a/app/assets/javascripts/discourse/app/models/bookmark.js
+++ b/app/assets/javascripts/discourse/app/models/bookmark.js
@@ -120,11 +120,17 @@ const Bookmark = RestModel.extend({
).capitalize();
},
- loadItems() {
- return ajax(`/u/${this.user.username}/bookmarks.json`, { cache: "false" });
+ loadItems(params) {
+ let url = `/u/${this.user.username}/bookmarks.json`;
+
+ if (params) {
+ url += "?" + $.param(params);
+ }
+
+ return ajax(url, { cache: "false" });
},
- loadMore() {
+ loadMore(additionalParams) {
if (!this.more_bookmarks_url) {
return Promise.resolve();
}
@@ -136,7 +142,15 @@ const Bookmark = RestModel.extend({
if (params) {
moreUrl += "?" + params;
}
+ if (additionalParams) {
+ if (moreUrl.includes("?")) {
+ moreUrl += "&" + $.param(additionalParams);
+ } else {
+ moreUrl += "?" + $.param(additionalParams);
+ }
+ }
}
+
return ajax({ url: moreUrl });
},
diff --git a/app/assets/javascripts/discourse/app/routes/user-activity-bookmarks-with-reminders.js b/app/assets/javascripts/discourse/app/routes/user-activity-bookmarks-with-reminders.js
index 7c5cf81ec61..a4883b7280f 100644
--- a/app/assets/javascripts/discourse/app/routes/user-activity-bookmarks-with-reminders.js
+++ b/app/assets/javascripts/discourse/app/routes/user-activity-bookmarks-with-reminders.js
@@ -1,6 +1,10 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
+ queryParams: {
+ q: { replace: true }
+ },
+
redirect() {
this.transitionTo("userActivity.bookmarks");
}
diff --git a/app/assets/javascripts/discourse/app/templates/user/bookmarks.hbs b/app/assets/javascripts/discourse/app/templates/user/bookmarks.hbs
index 33a43e6da20..b6a169bd387 100644
--- a/app/assets/javascripts/discourse/app/templates/user/bookmarks.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/bookmarks.hbs
@@ -1,3 +1,15 @@
+
+ {{input type="text"
+ value=searchTerm
+ placeholder=(i18n "bookmarks.search_placeholder")
+ enter=(action "search")
+ id="bookmark-search" autocomplete="discourse"}}
+ {{d-button
+ class="btn-primary"
+ action=(action "search")
+ type="button"
+ label="bookmarks.search"}}
+
{{#if noContent}}
{{noResultsHelp}}
{{else}}
diff --git a/app/serializers/user_bookmark_list_serializer.rb b/app/serializers/user_bookmark_list_serializer.rb
index 4e82e44b177..a7a4ed0fdae 100644
--- a/app/serializers/user_bookmark_list_serializer.rb
+++ b/app/serializers/user_bookmark_list_serializer.rb
@@ -6,6 +6,6 @@ class UserBookmarkListSerializer < ApplicationSerializer
has_many :bookmarks, serializer: UserBookmarkSerializer, embed: :objects
def include_more_bookmarks_url?
- object.bookmarks.size == object.per_page
+ @include_more_bookmarks_url ||= object.bookmarks.size == object.per_page
end
end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index da71b0f4af3..e20a7017317 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -320,6 +320,8 @@ en:
invalid_custom_datetime: "The date and time you provided is invalid, please try again."
list_permission_denied: "You do not have permission to view this user's bookmarks."
delete_when_reminder_sent: "Delete this bookmark when the reminder notification is sent."
+ search_placeholder: "Search bookmarks by name or post content"
+ search: "Search"
reminders:
at_desktop: "Next time I'm at my desktop"
later_today: "Later today"
diff --git a/db/migrate/20200713071305_add_bookmark_name_index.rb b/db/migrate/20200713071305_add_bookmark_name_index.rb
new file mode 100644
index 00000000000..1f7107bc842
--- /dev/null
+++ b/db/migrate/20200713071305_add_bookmark_name_index.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddBookmarkNameIndex < ActiveRecord::Migration[6.0]
+ def change
+ add_index :bookmarks, :name
+ end
+end
diff --git a/lib/bookmark_query.rb b/lib/bookmark_query.rb
index b5351f108ad..2f9daf52952 100644
--- a/lib/bookmark_query.rb
+++ b/lib/bookmark_query.rb
@@ -35,6 +35,10 @@ class BookmarkQuery
results = results.merge(Post.secured(@guardian))
+ if @params[:q].present?
+ results = results.where("bookmarks.name ILIKE :q OR posts.raw ILIKE :q", q: "%#{@params[:q]}%")
+ end
+
if @page.positive?
results = results.offset(@page * @params[:per_page])
end
diff --git a/spec/lib/bookmark_query_spec.rb b/spec/lib/bookmark_query_spec.rb
index de717575382..c1f932b804d 100644
--- a/spec/lib/bookmark_query_spec.rb
+++ b/spec/lib/bookmark_query_spec.rb
@@ -11,8 +11,9 @@ RSpec.describe BookmarkQuery do
end
describe "#list_all" do
- fab!(:bookmark1) { Fabricate(:bookmark, user: user) }
- fab!(:bookmark2) { Fabricate(:bookmark, user: user) }
+ fab!(:post) { Fabricate(:post, raw: "Some post content here") }
+ fab!(:bookmark1) { Fabricate(:bookmark, user: user, name: "Check up later") }
+ fab!(:bookmark2) { Fabricate(:bookmark, user: user, post: post, topic: post.topic) }
it "returns all the bookmarks for a user" do
expect(bookmark_query.list_all.count).to eq(2)
@@ -37,6 +38,18 @@ RSpec.describe BookmarkQuery do
expect(preloaded_bookmarks.any?).to eq(true)
end
+ context "when q param is provided" do
+ it "can search by post content" do
+ bookmarks = bookmark_query(params: { q: 'content' }).list_all
+ expect(bookmarks.map(&:id)).to eq([bookmark2.id])
+ end
+
+ it "can search by bookmark name" do
+ bookmarks = bookmark_query(params: { q: 'check' }).list_all
+ expect(bookmarks.map(&:id)).to eq([bookmark1.id])
+ end
+ end
+
context "for a whispered post" do
before do
bookmark1.post.update(post_type: Post.types[:whisper])