#23 Prevent bookmark duplicates

* Show hint if URL is already bookmarked

* Remove hint if URL belongs to edited bookmark

* Fix query param encoding

* Update bookmark instead of duplicating it

Co-authored-by: Sascha Ißbrücker <sissbruecker@lyska.io>
This commit is contained in:
Sascha Ißbrücker 2020-09-13 08:46:07 +02:00 committed by GitHub
parent 10fd3d89be
commit 348a536aa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 67 additions and 14 deletions

View File

@ -7,6 +7,14 @@ from bookmarks.services.website_loader import load_website_metadata
def create_bookmark(form: BookmarkForm, current_user: User): def create_bookmark(form: BookmarkForm, current_user: User):
# If URL is already bookmarked, then update it
existing_bookmark = Bookmark.objects.filter(owner=current_user, url=form.data['url']).first()
if existing_bookmark is not None:
update_form = BookmarkForm(data=form.data, instance=existing_bookmark)
update_bookmark(update_form, current_user)
return
bookmark = form.save(commit=False) bookmark = form.save(commit=False)
# Update website info # Update website info
_update_website_metadata(bookmark) _update_website_metadata(bookmark)

View File

@ -55,4 +55,15 @@ ul.bookmark-list {
.form-icon.loading { .form-icon.loading {
visibility: hidden; visibility: hidden;
} }
.form-input-hint.bookmark-exists {
visibility: hidden;
color: $warning-color;
a {
color: $warning-color;
text-decoration: underline;
font-weight: bold;
}
}
} }

View File

@ -8,7 +8,7 @@
<h2>Edit bookmark</h2> <h2>Edit bookmark</h2>
</div> </div>
<form action="{% url 'bookmarks:edit' bookmark_id %}" method="post" class="col-6 col-md-12" novalidate> <form action="{% url 'bookmarks:edit' bookmark_id %}" method="post" class="col-6 col-md-12" novalidate>
{% bookmark_form form all_tags %} {% bookmark_form form all_tags bookmark_id %}
</form> </form>
</section> </section>
</div> </div>

View File

@ -12,6 +12,9 @@
{{ form.url.errors }} {{ form.url.errors }}
</div> </div>
{% endif %} {% endif %}
<div class="form-input-hint bookmark-exists">
This URL is already bookmarked. You can <a href="#">edit</a> it or you can overwrite the existing bookmark by saving this form.
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="{{ form.tag_string.id_for_label }}" class="form-label">Tags</label> <label for="{{ form.tag_string.id_for_label }}" class="form-label">Tags</label>
@ -77,28 +80,42 @@
</script> </script>
<script type="application/javascript"> <script type="application/javascript">
/** /**
* Pre-fill title and description placeholders with metadata from website as soon as URL changes * - Pre-fill title and description placeholders with metadata from website as soon as URL changes
* - Show hint if URL is already bookmarked
*/ */
(function init() { (function init() {
const urlInput = document.getElementById('{{ form.url.id_for_label }}'); const urlInput = document.getElementById('{{ form.url.id_for_label }}');
const titleInput = document.getElementById('{{ form.title.id_for_label }}'); const titleInput = document.getElementById('{{ form.title.id_for_label }}');
const descriptionInput = document.getElementById('{{ form.description.id_for_label }}'); const descriptionInput = document.getElementById('{{ form.description.id_for_label }}');
const editedBookmarkId = {{ bookmark_id }}
urlInput.addEventListener('input', updateMetadata); urlInput.addEventListener('input', checkUrl);
function updateMetadata() { function checkUrl() {
toggleIcon(titleInput, true); toggleIcon(titleInput, true);
toggleIcon(descriptionInput, true); toggleIcon(descriptionInput, true);
const websiteUrl = urlInput.value; const websiteUrl = encodeURIComponent(urlInput.value);
const requestUrl = `{% url 'bookmarks:api.website_metadata' %}?url=${websiteUrl}`; const requestUrl = `{% url 'bookmarks:api.check_url' %}?url=${websiteUrl}`;
fetch(requestUrl) fetch(requestUrl)
.then(response => response.json()) .then(response => response.json())
.then(metadata => { .then(data => {
const metadata = data.metadata
titleInput.setAttribute('placeholder', metadata.title || ''); titleInput.setAttribute('placeholder', metadata.title || '');
descriptionInput.setAttribute('placeholder', metadata.description || ''); descriptionInput.setAttribute('placeholder', metadata.description || '');
toggleIcon(titleInput, false); toggleIcon(titleInput, false);
toggleIcon(descriptionInput, false); toggleIcon(descriptionInput, false);
// Display hint if URL is already bookmarked
const bookmarkExistsHint = document.querySelector('.form-input-hint.bookmark-exists')
const editExistingBookmarkLink = bookmarkExistsHint.querySelector('a')
if(data.bookmark && data.bookmark.id !== editedBookmarkId) {
bookmarkExistsHint.style['visibility'] = 'visible'
editExistingBookmarkLink.href = data.bookmark.edit_url
} else {
bookmarkExistsHint.style['visibility'] = 'hidden'
}
}); });
} }
@ -107,7 +124,7 @@
icon.style['visibility'] = show ? 'visible' : 'hidden'; icon.style['visibility'] = show ? 'visible' : 'hidden';
} }
if (urlInput.value) updateMetadata(); if (urlInput.value) checkUrl();
})(); })();
</script> </script>
</div> </div>

View File

@ -8,7 +8,7 @@
<h2>New bookmark</h2> <h2>New bookmark</h2>
</div> </div>
<form action="{% url 'bookmarks:new' %}" method="post" class="col-6 col-md-12" novalidate> <form action="{% url 'bookmarks:new' %}" method="post" class="col-6 col-md-12" novalidate>
{% bookmark_form form all_tags auto_close %} {% bookmark_form form all_tags auto_close=auto_close %}
</form> </form>
</section> </section>
</div> </div>

View File

@ -9,7 +9,7 @@ register = template.Library()
@register.inclusion_tag('bookmarks/form.html', name='bookmark_form') @register.inclusion_tag('bookmarks/form.html', name='bookmark_form')
def bookmark_form(form: BookmarkForm, all_tags: List[Tag], auto_close: bool = False): def bookmark_form(form: BookmarkForm, all_tags: List[Tag], bookmark_id: int = 0, auto_close: bool = False):
all_tag_names = [tag.name for tag in all_tags] all_tag_names = [tag.name for tag in all_tags]
all_tags_string = build_tag_string(all_tag_names, ' ') all_tags_string = build_tag_string(all_tag_names, ' ')
@ -17,7 +17,8 @@ def bookmark_form(form: BookmarkForm, all_tags: List[Tag], auto_close: bool = Fa
return { return {
'form': form, 'form': form,
'auto_close': auto_close, 'auto_close': auto_close,
'all_tags': all_tags_string 'all_tags': all_tags_string,
'bookmark_id': bookmark_id
} }

View File

@ -20,5 +20,5 @@ urlpatterns = [
path('settings/import', views.settings.bookmark_import, name='settings.import'), path('settings/import', views.settings.bookmark_import, name='settings.import'),
path('settings/export', views.settings.bookmark_export, name='settings.export'), path('settings/export', views.settings.bookmark_export, name='settings.export'),
# API # API
path('api/website_metadata', views.api.website_metadata, name='api.website_metadata'), path('api/check_url', views.api.check_url, name='api.check_url'),
] ]

View File

@ -1,11 +1,27 @@
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.forms import model_to_dict
from django.http import JsonResponse from django.http import JsonResponse
from django.urls import reverse
from bookmarks.services.website_loader import load_website_metadata from bookmarks.services.website_loader import load_website_metadata
from bookmarks.models import Bookmark
@login_required @login_required
def website_metadata(request): def check_url(request):
url = request.GET.get('url') url = request.GET.get('url')
bookmark = Bookmark.objects.filter(owner=request.user, url=url).first()
existing_bookmark_data = None
if bookmark is not None:
existing_bookmark_data = {
'id': bookmark.id,
'edit_url': reverse('bookmarks:edit', args=[bookmark.id])
}
metadata = load_website_metadata(url) metadata = load_website_metadata(url)
return JsonResponse(metadata.to_dict())
return JsonResponse({
'bookmark': existing_bookmark_data,
'metadata': metadata.to_dict()
})