mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-14 05:59:29 +02:00

* Allow marking bookmarks as shared * Add basic share view * Ensure tag names in tag cloud are unique * Filter shared bookmarks by user * Add link for filtering by user * Prevent n+1 queries when rendering bookmark list * Prevent empty query params in return URL * Fix user select template tag name * Create shared bookmarks through API * List shared bookmarks through API * Show bookmark suggestions for shared view * Show unique tags in search suggestions * Sort user options * Add bookmark sharing feature flag * Add test for share setting default * Simplify settings view
192 lines
8.4 KiB
HTML
192 lines
8.4 KiB
HTML
{% load widget_tweaks %}
|
|
{% load static %}
|
|
|
|
<div class="bookmarks-form">
|
|
{% csrf_token %}
|
|
{{ form.auto_close|attr:"type:hidden" }}
|
|
<div class="form-group {% if form.url.errors %}has-error{% endif %}">
|
|
<label for="{{ form.url.id_for_label }}" class="form-label">URL</label>
|
|
{{ form.url|add_class:"form-input"|attr:"autofocus"|attr:"placeholder: " }}
|
|
{% if form.url.errors %}
|
|
<div class="form-input-hint">
|
|
{{ form.url.errors }}
|
|
</div>
|
|
{% 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 class="form-group">
|
|
<label for="{{ form.tag_string.id_for_label }}" class="form-label">Tags</label>
|
|
{{ form.tag_string|add_class:"form-input"|attr:"autocomplete:off" }}
|
|
<div class="form-input-hint">
|
|
Enter any number of tags separated by space and <strong>without</strong> the hash (#). If a tag does not
|
|
exist it will be
|
|
automatically created.
|
|
</div>
|
|
{{ form.tag_string.errors }}
|
|
</div>
|
|
<div class="form-group has-icon-right">
|
|
<label for="{{ form.title.id_for_label }}" class="form-label">Title</label>
|
|
<div class="has-icon-right">
|
|
{{ form.title|add_class:"form-input"|attr:"autocomplete:off" }}
|
|
<i class="form-icon loading"></i>
|
|
<a class="btn btn-link form-icon" title="Edit title from website">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
|
|
<path fill-rule="evenodd"
|
|
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
|
clip-rule="evenodd"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
<div class="form-input-hint">
|
|
Optional, leave empty to use title from website.
|
|
</div>
|
|
{{ form.title.errors }}
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="{{ form.description.id_for_label }}" class="form-label">Description</label>
|
|
<div class="has-icon-right">
|
|
{{ form.description|add_class:"form-input"|attr:"rows:2" }}
|
|
<i class="form-icon loading"></i>
|
|
<a class="btn btn-link form-icon" title="Edit description from website">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"/>
|
|
<path fill-rule="evenodd"
|
|
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
|
|
clip-rule="evenodd"/>
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
<div class="form-input-hint">
|
|
Optional, leave empty to use description from website.
|
|
</div>
|
|
{{ form.description.errors }}
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="{{ form.unread.id_for_label }}" class="form-checkbox">
|
|
{{ form.unread }}
|
|
<i class="form-icon"></i>
|
|
<span>Mark as unread</span>
|
|
</label>
|
|
<div class="form-input-hint">
|
|
Unread bookmarks can be filtered for, and marked as read after you had a chance to look at them.
|
|
</div>
|
|
</div>
|
|
{% if request.user.profile.enable_sharing %}
|
|
<div class="form-group">
|
|
<label for="{{ form.shared.id_for_label }}" class="form-checkbox">
|
|
{{ form.shared }}
|
|
<i class="form-icon"></i>
|
|
<span>Share</span>
|
|
</label>
|
|
<div class="form-input-hint">
|
|
Share this bookmark with other users.
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<br/>
|
|
<div class="form-group">
|
|
{% if auto_close %}
|
|
<input type="submit" value="Save and close" class="btn btn-primary mr-2">
|
|
{% else %}
|
|
<input type="submit" value="Save" class="btn btn-primary mr-2">
|
|
{% endif %}
|
|
<a href="{{ cancel_url }}" class="btn">Nevermind</a>
|
|
</div>
|
|
|
|
{# Replace tag input with auto-complete component #}
|
|
<script src="{% static "bundle.js" %}"></script>
|
|
<script type="application/javascript">
|
|
const wrapper = document.createElement('div');
|
|
const tagInput = document.getElementById('{{ form.tag_string.id_for_label }}');
|
|
const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}')
|
|
|
|
new linkding.TagAutoComplete({
|
|
target: wrapper,
|
|
props: {
|
|
id: '{{ form.tag_string.id_for_label }}',
|
|
name: '{{ form.tag_string.name }}',
|
|
value: tagInput.value,
|
|
apiClient: apiClient
|
|
}
|
|
});
|
|
|
|
tagInput.parentElement.replaceChild(wrapper, tagInput);
|
|
</script>
|
|
<script type="application/javascript">
|
|
/**
|
|
* - Pre-fill title and description placeholders with metadata from website as soon as URL changes
|
|
* - Show hint if URL is already bookmarked
|
|
* - Setup buttons that allow editing of scraped website values
|
|
*/
|
|
(function init() {
|
|
const urlInput = document.getElementById('{{ form.url.id_for_label }}');
|
|
const titleInput = document.getElementById('{{ form.title.id_for_label }}');
|
|
const descriptionInput = document.getElementById('{{ form.description.id_for_label }}');
|
|
const editedBookmarkId = {{ bookmark_id }};
|
|
|
|
function toggleLoadingIcon(input, show) {
|
|
const icon = input.parentNode.querySelector('i.form-icon');
|
|
icon.style['visibility'] = show ? 'visible' : 'hidden';
|
|
}
|
|
|
|
function updatePlaceholder(input, value) {
|
|
if (value) {
|
|
input.setAttribute('placeholder', value);
|
|
} else {
|
|
input.removeAttribute('placeholder');
|
|
}
|
|
}
|
|
|
|
function checkUrl() {
|
|
toggleLoadingIcon(titleInput, true);
|
|
toggleLoadingIcon(descriptionInput, true);
|
|
updatePlaceholder(titleInput, null);
|
|
updatePlaceholder(descriptionInput, null);
|
|
|
|
const websiteUrl = encodeURIComponent(urlInput.value);
|
|
const requestUrl = `{% url 'bookmarks:api-root' %}bookmarks/check?url=${websiteUrl}`;
|
|
fetch(requestUrl)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const metadata = data.metadata;
|
|
updatePlaceholder(titleInput, metadata.title);
|
|
updatePlaceholder(descriptionInput, metadata.description);
|
|
toggleLoadingIcon(titleInput, false);
|
|
toggleLoadingIcon(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['display'] = 'block';
|
|
editExistingBookmarkLink.href = data.bookmark.edit_url;
|
|
} else {
|
|
bookmarkExistsHint.style['display'] = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupEditAutoValueButton(input) {
|
|
const editAutoValueButton = input.parentNode.querySelector('.btn.form-icon');
|
|
if (!editAutoValueButton) return;
|
|
editAutoValueButton.addEventListener('click', function (event) {
|
|
event.preventDefault();
|
|
input.value = input.getAttribute('placeholder');
|
|
input.focus();
|
|
input.select();
|
|
});
|
|
}
|
|
|
|
if (urlInput.value) checkUrl();
|
|
urlInput.addEventListener('input', checkUrl);
|
|
setupEditAutoValueButton(titleInput);
|
|
setupEditAutoValueButton(descriptionInput);
|
|
})();
|
|
</script>
|
|
</div>
|