#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:
parent
10fd3d89be
commit
348a536aa3
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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()
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in New Issue