Add notes to bookmarks (#472)

* Add basic bookmark notes

* Add bookmark list JS to shared bookmarks page

* Allow testing through ngrok

* Improve CSS

* Set notes through API

* Improve notes editing

* Improve notes icon

* Remove transitions for now

* Update keyboard shortcut

* Add bookmark list tests

* Add setting for showing notes permanently

* Add test for toggling notes

* Update API docs

* Allow searching for notes content

* Skip test
This commit is contained in:
Sascha Ißbrücker
2023-05-20 11:54:26 +02:00
committed by GitHub
parent 67ee896a46
commit 43115fd8f2
31 changed files with 609 additions and 144 deletions

View File

@@ -1,110 +1,128 @@
{% load static %}
{% load shared %}
{% load pagination %}
{% htmlmin %}
<ul class="bookmark-list">
{% for bookmark in bookmarks %}
<li data-is-bookmark-item>
<label class="form-checkbox bulk-edit-toggle">
<input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}">
<i class="form-icon"></i>
</label>
<div class="title">
<a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener"
class="{% if bookmark.unread %}text-italic{% endif %}">
{% if bookmark.favicon_file and request.user.profile.enable_favicons %}
<img src="{% static bookmark.favicon_file %}" alt="">
{% endif %}
{{ bookmark.resolved_title }}
</a>
</div>
{% if request.user.profile.display_url %}
<ul class="bookmark-list{% if request.user.profile.permanent_notes %} show-notes{% endif %}">
{% for bookmark in bookmarks %}
<li data-is-bookmark-item>
<label class="form-checkbox bulk-edit-toggle">
<input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}">
<i class="form-icon"></i>
</label>
<div class="title">
<a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener"
class="{% if bookmark.unread %}text-italic{% endif %}">
{% if bookmark.favicon_file and request.user.profile.enable_favicons %}
<img src="{% static bookmark.favicon_file %}" alt="">
{% endif %}
{{ bookmark.resolved_title }}
</a>
</div>
{% if request.user.profile.display_url %}
<div class="url-path truncate">
<a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener"
class="url-display text-sm">
<a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener"
class="url-display text-sm">
{{ bookmark.url }}
</a>
</div>
{% endif %}
<div class="description truncate">
{% if bookmark.tag_names %}
<span>
{% endif %}
<div class="description truncate">
{% if bookmark.tag_names %}
<span>
{% for tag_name in bookmark.tag_names %}
<a href="?{% add_tag_to_query tag_name %}">{{ tag_name|hash_tag }}</a>
{% endfor %}
</span>
{% endif %}
{% if bookmark.tag_names and bookmark.resolved_description %} | {% endif %}
{% if bookmark.resolved_description %}
<span>{{ bookmark.resolved_description }}</span>
{% endif %}
{% endif %}
{% if bookmark.tag_names and bookmark.resolved_description %} | {% endif %}
{% if bookmark.resolved_description %}
<span>{{ bookmark.resolved_description }}</span>
{% endif %}
</div>
{% if bookmark.notes %}
<div class="notes bg-gray text-gray-dark">
<div class="notes-content">
{% markdown bookmark.notes %}
</div>
</div>
<div class="actions">
{% if request.user.profile.bookmark_date_display == 'relative' %}
<span class="date-label text-gray text-sm">
{% endif %}
<div class="actions text-gray text-sm">
{% if request.user.profile.bookmark_date_display == 'relative' %}
<span>
{% if bookmark.web_archive_snapshot_url %}
<a href="{{ bookmark.web_archive_snapshot_url }}"
title="Show snapshot on the Internet Archive Wayback Machine" target="{{ link_target }}"
rel="noopener">
{% endif %}
<span>{{ bookmark.date_added|humanize_relative_date }}</span>
{% if bookmark.web_archive_snapshot_url %}
<span></span>
</a>
{% endif %}
</span>
<span class="text-gray text-sm">|</span>
{% endif %}
{% if request.user.profile.bookmark_date_display == 'absolute' %}
<span class="date-label text-gray text-sm">
{% if bookmark.web_archive_snapshot_url %}
<a href="{{ bookmark.web_archive_snapshot_url }}"
title="Show snapshot on the Internet Archive Wayback Machine" target="{{ link_target }}"
rel="noopener">
{% endif %}
<span>{{ bookmark.date_added|humanize_absolute_date }}</span>
{% if bookmark.web_archive_snapshot_url %}
<span></span>
</a>
{% endif %}
</span>
<span class="text-gray text-sm">|</span>
{% endif %}
{% if bookmark.owner == request.user %}
{# Bookmark owner actions #}
<a href="{% url 'bookmarks:edit' bookmark.id %}?return_url={{ return_url }}"
class="btn btn-link btn-sm">Edit</a>
{% if bookmark.is_archived %}
<button type="submit" name="unarchive" value="{{ bookmark.id }}"
class="btn btn-link btn-sm">Unarchive
</button>
{% else %}
<button type="submit" name="archive" value="{{ bookmark.id }}"
class="btn btn-link btn-sm">Archive
</button>
<span>{{ bookmark.date_added|humanize_relative_date }}</span>
{% if bookmark.web_archive_snapshot_url %}
</a>
{% endif %}
<button type="submit" name="remove" value="{{ bookmark.id }}"
class="btn btn-link btn-sm btn-confirmation">Remove
</span>
<span class="separator">|</span>
{% endif %}
{% if request.user.profile.bookmark_date_display == 'absolute' %}
<span>
{% if bookmark.web_archive_snapshot_url %}
<a href="{{ bookmark.web_archive_snapshot_url }}"
title="Show snapshot on the Internet Archive Wayback Machine" target="{{ link_target }}"
rel="noopener">
{% endif %}
<span>{{ bookmark.date_added|humanize_absolute_date }}</span>
{% if bookmark.web_archive_snapshot_url %}
</a>
{% endif %}
</span>
<span class="separator">|</span>
{% endif %}
{% if bookmark.owner == request.user %}
{# Bookmark owner actions #}
<a href="{% url 'bookmarks:edit' bookmark.id %}?return_url={{ return_url }}">Edit</a>
{% if bookmark.is_archived %}
<button type="submit" name="unarchive" value="{{ bookmark.id }}"
class="btn btn-link btn-sm">Unarchive
</button>
{% if bookmark.unread %}
<span class="text-gray text-sm">|</span>
<button type="submit" name="mark_as_read" value="{{ bookmark.id }}"
class="btn btn-link btn-sm">Mark as read
</button>
{% endif %}
{% else %}
{# Shared bookmark actions #}
<span class="text-gray text-sm">Shared by
<a class="text-gray"
href="?{% replace_query_param user=bookmark.owner.username %}">{{ bookmark.owner.username }}</a>
</span>
<button type="submit" name="archive" value="{{ bookmark.id }}"
class="btn btn-link btn-sm">Archive
</button>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
<button type="submit" name="remove" value="{{ bookmark.id }}"
class="btn btn-link btn-sm btn-confirmation">Remove
</button>
{% if bookmark.unread %}
<span class="separator">|</span>
<button type="submit" name="mark_as_read" value="{{ bookmark.id }}"
class="btn btn-link btn-sm">Mark as read
</button>
{% endif %}
{% else %}
{# Shared bookmark actions #}
<span>Shared by
<a href="?{% replace_query_param user=bookmark.owner.username %}">{{ bookmark.owner.username }}</a>
</span>
{% endif %}
{% if bookmark.notes and not request.user.profile.permanent_notes %}
<span class="separator">|</span>
<button class="btn btn-link btn-sm toggle-notes" title="Toggle notes">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-notes" width="16" height="16"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M5 3m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z"></path>
<path d="M9 7l6 0"></path>
<path d="M9 11l6 0"></path>
<path d="M9 15l4 0"></path>
</svg>
<span>Notes</span>
</button>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
<div class="bookmark-pagination">
{% pagination bookmarks %}
</div>
{% endhtmlmin %}
<div class="bookmark-pagination">
{% pagination bookmarks %}
</div>

View File

@@ -67,6 +67,19 @@
</div>
{{ form.description.errors }}
</div>
<div class="form-group">
<details class="notes"{% if form.has_notes %} open{% endif %}>
<summary>
<span class="form-label d-inline-block">Notes</span>
</summary>
<label for="{{ form.notes.id_for_label }}" class="text-assistive">Notes</label>
{{ form.notes|add_class:"form-input"|attr:"rows:8" }}
<div class="form-input-hint">
Additional notes, supports Markdown.
</div>
{{ form.notes.errors }}
</details>
</div>
<div class="form-group">
<label for="{{ form.unread.id_for_label }}" class="form-checkbox">
{{ form.unread }}
@@ -128,6 +141,8 @@
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 notesDetails = document.querySelector('form details.notes');
const notesInput = document.getElementById('{{ form.notes.id_for_label }}');
const tagsInput = document.getElementById('{{ form.tag_string.id_for_label }}');
const unreadCheckbox = document.getElementById('{{ form.unread.id_for_label }}');
const sharedCheckbox = document.getElementById('{{ form.shared.id_for_label }}');
@@ -149,11 +164,17 @@
}
function updateInput(input, value) {
input.value = value;
if (!input) {
return;
}
input.value = value;
}
function updateCheckbox(input, value) {
input.checked = value;
if (!input) {
return;
}
input.checked = value;
}
function checkUrl() {
@@ -179,8 +200,10 @@
if (existingBookmark && !editedBookmarkId) {
bookmarkExistsHint.style['display'] = 'block';
notesDetails.open = !!existingBookmark.notes;
updateInput(titleInput, existingBookmark.title);
updateInput(descriptionInput, existingBookmark.description);
updateInput(notesInput, existingBookmark.notes);
updateInput(tagsInput, existingBookmark.tag_names.join(" "));
updateCheckbox(unreadCheckbox, existingBookmark.unread);
updateCheckbox(sharedCheckbox, existingBookmark.shared);
@@ -201,6 +224,9 @@
});
}
setupEditAutoValueButton(titleInput);
setupEditAutoValueButton(descriptionInput);
// Fetch initial website data if we have a URL, and we are not editing an existing bookmark
// For existing bookmarks we get the website metadata through hidden inputs
if (urlInput.value && !editedBookmarkId) {
@@ -213,9 +239,6 @@
updatePlaceholder(titleInput, websiteTitleInput.value);
updatePlaceholder(descriptionInput, websiteDescriptionInput.value);
}
setupEditAutoValueButton(titleInput);
setupEditAutoValueButton(descriptionInput);
})();
</script>
</div>

View File

@@ -45,4 +45,5 @@
<script src="{% static "bundle.js" %}"></script>
<script src="{% static "shared.js" %}"></script>
<script src="{% static "bookmark_list.js" %}"></script>
{% endblock %}

View File

@@ -38,6 +38,16 @@
When enabled, this setting displays the bookmark URL below the title.
</div>
</div>
<div class="form-group">
<label for="{{ form.permanent_notes.id_for_label }}" class="form-checkbox">
{{ form.permanent_notes }}
<i class="form-icon"></i> Show notes permanently
</label>
<div class="form-input-hint">
Whether to show bookmark notes permanently, without having to toggle them individually.
Alternatively the keyboard shortcut <code>e</code> can be used to temporarily show all notes.
</div>
</div>
<div class="form-group">
<label for="{{ form.bookmark_link_target.id_for_label }}" class="form-label">Open bookmarks in</label>
{{ form.bookmark_link_target|add_class:"form-select col-2 col-sm-12" }}