From c13b27e170e5f1b5889368e10af82476d377ed9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Thu, 31 Dec 2020 09:47:51 +0100 Subject: [PATCH] Add search autocomplete (#53) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement search autocomplete for recent searches * Implement search autocomplete for bookmarks * Fix URL encoding of query param * Add tag suggestions to search autocomplete Co-authored-by: Sascha Ißbrücker --- API.md | 2 + bookmarks/api/serializers.py | 19 +- .../components/SearchAutoComplete.svelte | 274 ++++++++++++++++++ bookmarks/components/SearchHistory.js | 48 +++ bookmarks/components/TagAutocomplete.svelte | 25 +- bookmarks/components/api.js | 14 + bookmarks/components/index.js | 6 +- bookmarks/components/util.js | 37 +++ bookmarks/styles/bookmarks.scss | 7 +- bookmarks/templates/bookmarks/index.html | 26 +- bookmarks/urls.py | 2 +- bookmarks/views/bookmarks.py | 3 + linkdings.iml | 1 + 13 files changed, 436 insertions(+), 28 deletions(-) create mode 100644 bookmarks/components/SearchAutoComplete.svelte create mode 100644 bookmarks/components/SearchHistory.js create mode 100644 bookmarks/components/api.js create mode 100644 bookmarks/components/util.js diff --git a/API.md b/API.md index 70a59e4..a3bbfbf 100644 --- a/API.md +++ b/API.md @@ -45,6 +45,8 @@ Example response: "url": "https://example.com", "title": "Example title", "description": "Example description", + "website_title": "Website title", + "website_description": "Website description", "tag_names": [ "tag1", "tag2" diff --git a/bookmarks/api/serializers.py b/bookmarks/api/serializers.py index d867706..6492b9e 100644 --- a/bookmarks/api/serializers.py +++ b/bookmarks/api/serializers.py @@ -12,8 +12,23 @@ class TagListField(serializers.ListField): class BookmarkSerializer(serializers.ModelSerializer): class Meta: model = Bookmark - fields = ['id', 'url', 'title', 'description', 'tag_names', 'date_added', 'date_modified'] - read_only_fields = ['date_added', 'date_modified'] + fields = [ + 'id', + 'url', + 'title', + 'description', + 'website_title', + 'website_description', + 'tag_names', + 'date_added', + 'date_modified' + ] + read_only_fields = [ + 'website_title', + 'website_description', + 'date_added', + 'date_modified' + ] # Override readonly tag_names property to allow passing a list of tag names to create/update tag_names = TagListField() diff --git a/bookmarks/components/SearchAutoComplete.svelte b/bookmarks/components/SearchAutoComplete.svelte new file mode 100644 index 0000000..a911b88 --- /dev/null +++ b/bookmarks/components/SearchAutoComplete.svelte @@ -0,0 +1,274 @@ + + +
+
+ +
+ + +
+ + \ No newline at end of file diff --git a/bookmarks/components/SearchHistory.js b/bookmarks/components/SearchHistory.js new file mode 100644 index 0000000..c1821fd --- /dev/null +++ b/bookmarks/components/SearchHistory.js @@ -0,0 +1,48 @@ +const SEARCH_HISTORY_KEY = 'searchHistory' +const MAX_ENTRIES = 30 + +export class SearchHistory { + + getHistory() { + const historyJson = localStorage.getItem(SEARCH_HISTORY_KEY) + return historyJson ? JSON.parse(historyJson) : { + recent: [] + } + } + + pushCurrent() { + // Skip if browser is not compatible + if (!window.URLSearchParams) return + const urlParams = new URLSearchParams(window.location.search); + const searchParam = urlParams.get('q'); + + if (!searchParam) return + + this.push(searchParam) + } + + push(search) { + const history = this.getHistory() + + history.recent.unshift(search) + + // Remove duplicates and clamp to max entries + history.recent = history.recent.reduce((acc, cur) => { + if (acc.length >= MAX_ENTRIES) return acc + if (acc.indexOf(cur) >= 0) return acc + acc.push(cur) + return acc + }, []) + + const newHistoryJson = JSON.stringify(history) + localStorage.setItem(SEARCH_HISTORY_KEY, newHistoryJson) + } + + getRecentSearches(query, max) { + const history = this.getHistory() + + return history.recent + .filter(search => !query || search.toLowerCase().indexOf(query.toLowerCase()) >= 0) + .slice(0, max) + } +} \ No newline at end of file diff --git a/bookmarks/components/TagAutocomplete.svelte b/bookmarks/components/TagAutocomplete.svelte index 0616ebd..efbdd18 100644 --- a/bookmarks/components/TagAutocomplete.svelte +++ b/bookmarks/components/TagAutocomplete.svelte @@ -1,4 +1,6 @@ + {% endblock %} diff --git a/bookmarks/urls.py b/bookmarks/urls.py index f0cc8eb..41a0499 100644 --- a/bookmarks/urls.py +++ b/bookmarks/urls.py @@ -22,5 +22,5 @@ urlpatterns = [ path('settings/export', views.settings.bookmark_export, name='settings.export'), # API path('api/check_url', views.api.check_url, name='api.check_url'), - url(r'^api/', include(router.urls)) + path('api/', include(router.urls), name='api') ] diff --git a/bookmarks/views/bookmarks.py b/bookmarks/views/bookmarks.py index 08b60c5..3e6aea6 100644 --- a/bookmarks/views/bookmarks.py +++ b/bookmarks/views/bookmarks.py @@ -22,6 +22,8 @@ def index(request): paginator = Paginator(query_set, _default_page_size) bookmarks = paginator.get_page(page) tags = queries.query_tags(request.user, query_string) + tag_names = [tag.name for tag in tags] + tags_string = build_tag_string(tag_names, ' ') return_url = generate_index_return_url(page, query_string) if request.GET.get('tag'): @@ -32,6 +34,7 @@ def index(request): context = { 'bookmarks': bookmarks, 'tags': tags, + 'tags_string': tags_string, 'query': query_string if query_string else '', 'empty': paginator.count == 0, 'return_url': return_url diff --git a/linkdings.iml b/linkdings.iml index ac0262f..24f507f 100644 --- a/linkdings.iml +++ b/linkdings.iml @@ -19,6 +19,7 @@ +