Minify bookmark list HTML (#332)

This commit is contained in:
Sascha Ißbrücker
2022-09-04 09:03:14 +02:00
committed by GitHub
parent 6e0a345c2c
commit 38f4dd2bea
18 changed files with 699 additions and 654 deletions

View File

@@ -5,42 +5,42 @@
{% block content %} {% block content %}
{% include 'bookmarks/bulk_edit/state.html' %} {% include 'bookmarks/bulk_edit/state.html' %}
<div class="bookmarks-page columns"> <div class="bookmarks-page columns">
{# Bookmark list #} {# Bookmark list #}
<section class="content-area column col-8 col-md-12"> <section class="content-area column col-8 col-md-12">
<div class="content-area-header"> <div class="content-area-header">
<h2>Archived bookmarks</h2> <h2>Archived bookmarks</h2>
<div class="spacer"></div> <div class="spacer"></div>
{% bookmark_search filters tags mode='archived' %} {% bookmark_search filters tags mode='archived' %}
{% include 'bookmarks/bulk_edit/toggle.html' %} {% include 'bookmarks/bulk_edit/toggle.html' %}
</div> </div>
<form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ return_url }}" <form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ return_url }}"
method="post"> method="post">
{% csrf_token %} {% csrf_token %}
{% include 'bookmarks/bulk_edit/bar.html' with mode='archive' %} {% include 'bookmarks/bulk_edit/bar.html' with mode='archive' %}
{% if empty %} {% if empty %}
{% include 'bookmarks/empty_bookmarks.html' %} {% include 'bookmarks/empty_bookmarks.html' %}
{% else %} {% else %}
{% bookmark_list bookmarks return_url link_target %} {% bookmark_list bookmarks return_url link_target %}
{% endif %} {% endif %}
</form> </form>
</section> </section>
{# Tag list #} {# Tag list #}
<section class="content-area column col-4 hide-md"> <section class="content-area column col-4 hide-md">
<div class="content-area-header"> <div class="content-area-header">
<h2>Tags</h2> <h2>Tags</h2>
</div> </div>
{% tag_cloud tags selected_tags %} {% tag_cloud tags selected_tags %}
</section> </section>
</div> </div>
<script src="{% static "bundle.js" %}"></script> <script src="{% static "bundle.js" %}"></script>
<script src="{% static "shared.js" %}"></script> <script src="{% static "shared.js" %}"></script>
<script src="{% static "bookmark_list.js" %}"></script> <script src="{% static "bookmark_list.js" %}"></script>
{% endblock %} {% endblock %}

View File

@@ -1,91 +1,98 @@
{% load shared %} {% load shared %}
{% load pagination %} {% load pagination %}
{% htmlmin %}
<ul class="bookmark-list"> <ul class="bookmark-list">
{% for bookmark in bookmarks %} {% for bookmark in bookmarks %}
<li data-is-bookmark-item> <li data-is-bookmark-item>
<label class="form-checkbox bulk-edit-toggle"> <label class="form-checkbox bulk-edit-toggle">
<input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}"> <input type="checkbox" name="bookmark_id" value="{{ bookmark.id }}">
<i class="form-icon"></i> <i class="form-icon"></i>
</label> </label>
<div class="title"> <div class="title">
<a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener" <a href="{{ bookmark.url }}" target="{{ link_target }}" rel="noopener"
class="{% if bookmark.unread %}text-italic{% endif %}"> class="{% if bookmark.unread %}text-italic{% endif %}">
{{ bookmark.resolved_title }} {{ bookmark.resolved_title }}
</a>
</div>
<div class="description truncate">
{% if bookmark.tag_names %}
<span>
{% for tag_name in bookmark.tag_names %}
<a href="?{% append_to_query_param q=tag_name|hash_tag %}">{{ 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 %}
</div>
<div class="actions">
{% if request.user.profile.bookmark_date_display == 'relative' %}
<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_relative_date }}</span>
{% if bookmark.web_archive_snapshot_url %}
<span></span>
</a> </a>
</div> {% endif %}
<div class="description truncate"> </span>
{% if bookmark.tag_names %} <span class="text-gray text-sm">|</span>
<span> {% endif %}
{% for tag_name in bookmark.tag_names %} {% if request.user.profile.bookmark_date_display == 'absolute' %}
<a href="?{% append_to_query_param q=tag_name|hash_tag %}">{{ tag_name|hash_tag }}</a> <span class="date-label text-gray text-sm">
{% endfor %} {% if bookmark.web_archive_snapshot_url %}
</span> <a href="{{ bookmark.web_archive_snapshot_url }}"
{% endif %} title="Show snapshot on the Internet Archive Wayback Machine" target="{{ link_target }}"
{% if bookmark.tag_names and bookmark.resolved_description %} | {% endif %} rel="noopener">
{% endif %}
{% if bookmark.resolved_description %} <span>{{ bookmark.date_added|humanize_absolute_date }}</span>
<span>{{ bookmark.resolved_description }}</span> {% if bookmark.web_archive_snapshot_url %}
{% endif %} <span></span>
</div> </a>
<div class="actions"> {% endif %}
{% if request.user.profile.bookmark_date_display == 'relative' %} </span>
<span class="date-label text-gray text-sm"> <span class="text-gray text-sm">|</span>
{% if bookmark.web_archive_snapshot_url %} {% endif %}
<a href="{{ bookmark.web_archive_snapshot_url }}" {% if bookmark.owner == request.user %}
title="Show snapshot on the Internet Archive Wayback Machine" target="{{ link_target }}" rel="noopener"> {# Bookmark owner actions #}
{% endif %} <a href="{% url 'bookmarks:edit' bookmark.id %}?return_url={{ return_url }}"
<span>{{ bookmark.date_added|humanize_relative_date }}</span> class="btn btn-link btn-sm">Edit</a>
{% if bookmark.web_archive_snapshot_url %} {% if bookmark.is_archived %}
<span></span> <button type="submit" name="unarchive" value="{{ bookmark.id }}"
</a> class="btn btn-link btn-sm">Unarchive
{% endif %} </button>
</span> {% else %}
<span class="text-gray text-sm">|</span> <button type="submit" name="archive" value="{{ bookmark.id }}"
{% endif %} class="btn btn-link btn-sm">Archive
{% if request.user.profile.bookmark_date_display == 'absolute' %} </button>
<span class="date-label text-gray text-sm"> {% endif %}
{% if bookmark.web_archive_snapshot_url %} <button type="submit" name="remove" value="{{ bookmark.id }}"
<a href="{{ bookmark.web_archive_snapshot_url }}" class="btn btn-link btn-sm btn-confirmation">Remove
title="Show snapshot on the Internet Archive Wayback Machine" target="{{ link_target }}" rel="noopener"> </button>
{% endif %} {% if bookmark.unread %}
<span>{{ bookmark.date_added|humanize_absolute_date }}</span> <span class="text-gray text-sm">|</span>
{% if bookmark.web_archive_snapshot_url %} <button type="submit" name="mark_as_read" value="{{ bookmark.id }}"
<span></span> class="btn btn-link btn-sm">Mark as read
</a> </button>
{% endif %} {% endif %}
</span> {% else %}
<span class="text-gray text-sm">|</span> {# Shared bookmark actions #}
{% endif %} <span class="text-gray text-sm">Shared by
{% if bookmark.owner == request.user %} <a class="text-gray"
{# Bookmark owner actions #} href="?{% replace_query_param user=bookmark.owner.username %}">{{ bookmark.owner.username }}</a>
<a href="{% url 'bookmarks:edit' bookmark.id %}?return_url={{ return_url }}" </span>
class="btn btn-link btn-sm">Edit</a> {% endif %}
{% if bookmark.is_archived %} </div>
<button type="submit" name="unarchive" value="{{ bookmark.id }}" </li>
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>
{% endif %}
<button type="submit" name="remove" value="{{ bookmark.id }}"
class="btn btn-link btn-sm btn-confirmation">Remove</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>
{% endif %}
</div>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
<div class="bookmark-pagination"> <div class="bookmark-pagination">
{% pagination bookmarks %} {% pagination bookmarks %}
</div> </div>
{% endhtmlmin %}

View File

@@ -1,9 +1,9 @@
(function() { (function () {
var bookmarkUrl = window.location; var bookmarkUrl = window.location;
var applicationUrl = '{{ application_url }}'; var applicationUrl = '{{ application_url }}';
applicationUrl += '?url=' + encodeURIComponent(bookmarkUrl); applicationUrl += '?url=' + encodeURIComponent(bookmarkUrl);
applicationUrl += '&auto_close'; applicationUrl += '&auto_close';
window.open(applicationUrl); window.open(applicationUrl);
})(); })();

View File

@@ -1,31 +1,34 @@
{% load shared %}
{% htmlmin %}
<div class="bulk-edit-bar"> <div class="bulk-edit-bar">
<div class="bulk-edit-actions bg-gray"> <div class="bulk-edit-actions bg-gray">
<label class="form-checkbox bulk-edit-all-toggle"> <label class="form-checkbox bulk-edit-all-toggle">
<input type="checkbox" style="display: none"> <input type="checkbox" style="display: none">
<i class="form-icon"></i> <i class="form-icon"></i>
</label> </label>
{% if mode == 'archive' %} {% if mode == 'archive' %}
<button type="submit" name="bulk_unarchive" class="btn btn-link btn-sm btn-confirmation" <button type="submit" name="bulk_unarchive" class="btn btn-link btn-sm btn-confirmation"
title="Unarchive selected bookmarks">Unarchive title="Unarchive selected bookmarks">Unarchive
</button> </button>
{% else %} {% else %}
<button type="submit" name="bulk_archive" class="btn btn-link btn-sm btn-confirmation" <button type="submit" name="bulk_archive" class="btn btn-link btn-sm btn-confirmation"
title="Archive selected bookmarks">Archive title="Archive selected bookmarks">Archive
</button> </button>
{% endif %} {% endif %}
<span class="text-sm text-gray-dark"></span> <span class="text-sm text-gray-dark"></span>
<button type="submit" name="bulk_delete" class="btn btn-link btn-sm btn-confirmation" <button type="submit" name="bulk_delete" class="btn btn-link btn-sm btn-confirmation"
title="Delete selected bookmarks">Delete title="Delete selected bookmarks">Delete
</button> </button>
<span class="text-sm text-gray-dark"></span> <span class="text-sm text-gray-dark"></span>
<span class="text-sm text-gray-dark"><label for="bulk-edit-tags-input">Tags:</label></span> <span class="text-sm text-gray-dark"><label for="bulk-edit-tags-input">Tags:</label></span>
<input id="bulk-edit-tags-input" name="bulk_tag_string" class="form-input input-sm" <input id="bulk-edit-tags-input" name="bulk_tag_string" class="form-input input-sm"
placeholder="&nbsp;"> placeholder="&nbsp;">
<button type="submit" name="bulk_tag" class="btn btn-link btn-sm" <button type="submit" name="bulk_tag" class="btn btn-link btn-sm"
title="Add tags to selected bookmarks">Add title="Add tags to selected bookmarks">Add
</button> </button>
<button type="submit" name="bulk_untag" class="btn btn-link btn-sm" <button type="submit" name="bulk_untag" class="btn btn-link btn-sm"
title="Remove tags from selected bookmarks">Remove title="Remove tags from selected bookmarks">Remove
</button> </button>
</div> </div>
</div> </div>
{% endhtmlmin %}

View File

@@ -2,7 +2,8 @@
<span class="btn" title="Bulk edit"> <span class="btn" title="Bulk edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="20px" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="20px"
height="20px"> height="20px">
<path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/> <path
d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
</svg> </svg>
</span> </span>
</label> </label>

View File

@@ -1,9 +1,9 @@
{% extends "bookmarks/layout.html" %} {% extends "bookmarks/layout.html" %}
{% block content %} {% block content %}
<script type="application/javascript"> <script type="application/javascript">
window.close() window.close()
</script> </script>
<p>You can now close this window.</p> <p>You can now close this window.</p>
{% endblock %} {% endblock %}

View File

@@ -2,14 +2,15 @@
{% load bookmarks %} {% load bookmarks %}
{% block content %} {% block content %}
<div class="columns"> <div class="columns">
<section class="content-area column col-12"> <section class="content-area column col-12">
<div class="content-area-header"> <div class="content-area-header">
<h2>Edit bookmark</h2> <h2>Edit bookmark</h2>
</div> </div>
<form action="{% url 'bookmarks:edit' bookmark_id %}?return_url={{ return_url|urlencode }}" method="post" class="col-6 col-md-12" novalidate> <form action="{% url 'bookmarks:edit' bookmark_id %}?return_url={{ return_url|urlencode }}" method="post"
{% bookmark_form form return_url bookmark_id %} class="col-6 col-md-12" novalidate>
</form> {% bookmark_form form return_url bookmark_id %}
</section> </form>
</div> </section>
</div>
{% endblock %} {% endblock %}

View File

@@ -1,8 +1,9 @@
<div class="empty"> <div class="empty">
<p class="empty-title h5">You have no bookmarks yet</p> <p class="empty-title h5">You have no bookmarks yet</p>
<p class="empty-subtitle"> <p class="empty-subtitle">
You can get started by <a href="{% url 'bookmarks:new' %}">adding</a> bookmarks, You can get started by <a href="{% url 'bookmarks:new' %}">adding</a> bookmarks,
<a href="{% url 'bookmarks:settings.general' %}">importing</a> your existing bookmarks or configuring the <a href="{% url 'bookmarks:settings.general' %}">importing</a> your existing bookmarks or configuring the
<a href="{% url 'bookmarks:settings.integrations' %}">browser extension</a> or the <a href="{% url 'bookmarks:settings.integrations' %}">bookmarklet</a>. <a href="{% url 'bookmarks:settings.integrations' %}">browser extension</a> or the <a
</p> href="{% url 'bookmarks:settings.integrations' %}">bookmarklet</a>.
</p>
</div> </div>

View File

@@ -2,205 +2,205 @@
{% load static %} {% load static %}
<div class="bookmarks-form"> <div class="bookmarks-form">
{% csrf_token %} {% csrf_token %}
{{ form.website_title }} {{ form.website_title }}
{{ form.website_description }} {{ form.website_description }}
{{ form.auto_close|attr:"type:hidden" }} {{ form.auto_close|attr:"type:hidden" }}
<div class="form-group {% if form.url.errors %}has-error{% endif %}"> <div class="form-group {% if form.url.errors %}has-error{% endif %}">
<label for="{{ form.url.id_for_label }}" class="form-label">URL</label> <label for="{{ form.url.id_for_label }}" class="form-label">URL</label>
{{ form.url|add_class:"form-input"|attr:"autofocus"|attr:"placeholder: " }} {{ form.url|add_class:"form-input"|attr:"autofocus"|attr:"placeholder: " }}
{% if form.url.errors %} {% 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"> <div class="form-input-hint">
Unread bookmarks can be filtered for, and marked as read after you had a chance to look at them. {{ form.url.errors }}
</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> </div>
{% endif %} {% endif %}
<br/> <div class="form-input-hint bookmark-exists">
<div class="form-group"> This URL is already bookmarked. You can <a href="#">edit</a> it or you can overwrite the existing bookmark
{% if auto_close %} by saving this form.
<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> </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 #} {# Replace tag input with auto-complete component #}
<script src="{% static "bundle.js" %}"></script> <script src="{% static "bundle.js" %}"></script>
<script type="application/javascript"> <script type="application/javascript">
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
const tagInput = document.getElementById('{{ form.tag_string.id_for_label }}'); const tagInput = document.getElementById('{{ form.tag_string.id_for_label }}');
const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}') const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}')
new linkding.TagAutoComplete({ new linkding.TagAutoComplete({
target: wrapper, target: wrapper,
props: { props: {
id: '{{ form.tag_string.id_for_label }}', id: '{{ form.tag_string.id_for_label }}',
name: '{{ form.tag_string.name }}', name: '{{ form.tag_string.name }}',
value: tagInput.value, value: tagInput.value,
apiClient: apiClient 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 websiteTitleInput = document.getElementById('{{ form.website_title.id_for_label }}');
const websiteDescriptionInput = document.getElementById('{{ form.website_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();
}); });
}
tagInput.parentElement.replaceChild(wrapper, tagInput); // Fetch initial website data if we have a URL, and we are not editing an existing bookmark
</script> // For existing bookmarks we get the website metadata through hidden inputs
<script type="application/javascript"> if (urlInput.value && !editedBookmarkId) {
/** checkUrl();
* - Pre-fill title and description placeholders with metadata from website as soon as URL changes }
* - Show hint if URL is already bookmarked urlInput.addEventListener('input', checkUrl);
* - 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 websiteTitleInput = document.getElementById('{{ form.website_title.id_for_label }}');
const websiteDescriptionInput = document.getElementById('{{ form.website_description.id_for_label }}');
const editedBookmarkId = {{ bookmark_id }};
function toggleLoadingIcon(input, show) { // Set initial website title and description for edited bookmarks
const icon = input.parentNode.querySelector('i.form-icon'); if (editedBookmarkId) {
icon.style['visibility'] = show ? 'visible' : 'hidden'; updatePlaceholder(titleInput, websiteTitleInput.value);
} updatePlaceholder(descriptionInput, websiteDescriptionInput.value);
}
function updatePlaceholder(input, value) { setupEditAutoValueButton(titleInput);
if (value) { setupEditAutoValueButton(descriptionInput);
input.setAttribute('placeholder', value); })();
} else { </script>
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();
});
}
// 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) {
checkUrl();
}
urlInput.addEventListener('input', checkUrl);
// Set initial website title and description for edited bookmarks
if (editedBookmarkId) {
updatePlaceholder(titleInput, websiteTitleInput.value);
updatePlaceholder(descriptionInput, websiteDescriptionInput.value);
}
setupEditAutoValueButton(titleInput);
setupEditAutoValueButton(descriptionInput);
})();
</script>
</div> </div>

View File

@@ -5,42 +5,42 @@
{% block content %} {% block content %}
{% include 'bookmarks/bulk_edit/state.html' %} {% include 'bookmarks/bulk_edit/state.html' %}
<div class="bookmarks-page columns"> <div class="bookmarks-page columns">
{# Bookmark list #} {# Bookmark list #}
<section class="content-area column col-8 col-md-12"> <section class="content-area column col-8 col-md-12">
<div class="content-area-header"> <div class="content-area-header">
<h2>Bookmarks</h2> <h2>Bookmarks</h2>
<div class="spacer"></div> <div class="spacer"></div>
{% bookmark_search filters tags %} {% bookmark_search filters tags %}
{% include 'bookmarks/bulk_edit/toggle.html' %} {% include 'bookmarks/bulk_edit/toggle.html' %}
</div> </div>
<form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ return_url }}" <form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ return_url }}"
method="post"> method="post">
{% csrf_token %} {% csrf_token %}
{% include 'bookmarks/bulk_edit/bar.html' with mode='default' %} {% include 'bookmarks/bulk_edit/bar.html' with mode='default' %}
{% if empty %} {% if empty %}
{% include 'bookmarks/empty_bookmarks.html' %} {% include 'bookmarks/empty_bookmarks.html' %}
{% else %} {% else %}
{% bookmark_list bookmarks return_url link_target %} {% bookmark_list bookmarks return_url link_target %}
{% endif %} {% endif %}
</form> </form>
</section> </section>
{# Tag list #} {# Tag list #}
<section class="content-area column col-4 hide-md"> <section class="content-area column col-4 hide-md">
<div class="content-area-header"> <div class="content-area-header">
<h2>Tags</h2> <h2>Tags</h2>
</div> </div>
{% tag_cloud tags selected_tags %} {% tag_cloud tags selected_tags %}
</section> </section>
</div> </div>
<script src="{% static "bundle.js" %}"></script> <script src="{% static "bundle.js" %}"></script>
<script src="{% static "shared.js" %}"></script> <script src="{% static "shared.js" %}"></script>
<script src="{% static "bookmark_list.js" %}"></script> <script src="{% static "bookmark_list.js" %}"></script>
{% endblock %} {% endblock %}

View File

@@ -5,61 +5,61 @@
{# Use data attributes as storage for access in static scripts #} {# Use data attributes as storage for access in static scripts #}
<html lang="en" data-api-base-url="{% url 'bookmarks:api-root' %}"> <html lang="en" data-api-base-url="{% url 'bookmarks:api-root' %}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="{% static 'favicon.png' %}"/> <link rel="icon" href="{% static 'favicon.png' %}"/>
<link rel="apple-touch-icon" href="{% static 'apple-touch-icon.png' %}"> <link rel="apple-touch-icon" href="{% static 'apple-touch-icon.png' %}">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
<meta name="description" content="Self-hosted bookmark service"> <meta name="description" content="Self-hosted bookmark service">
<meta name="robots" content="index,follow"> <meta name="robots" content="index,follow">
<meta name="author" content="Sascha Ißbrücker"> <meta name="author" content="Sascha Ißbrücker">
<title>linkding</title> <title>linkding</title>
{# Include SASS styles, files are resolved from bookmarks/styles #} {# Include SASS styles, files are resolved from bookmarks/styles #}
{# Include specific theme variant based on user profile setting #} {# Include specific theme variant based on user profile setting #}
{% if request.user.profile.theme == 'light' %} {% if request.user.profile.theme == 'light' %}
<link href="{% sass_src 'theme-light.scss' %}" rel="stylesheet" type="text/css"/> <link href="{% sass_src 'theme-light.scss' %}" rel="stylesheet" type="text/css"/>
{% elif request.user.profile.theme == 'dark' %} {% elif request.user.profile.theme == 'dark' %}
<link href="{% sass_src 'theme-dark.scss' %}" rel="stylesheet" type="text/css"/> <link href="{% sass_src 'theme-dark.scss' %}" rel="stylesheet" type="text/css"/>
{% else %} {% else %}
{# Use auto theme as fallback #} {# Use auto theme as fallback #}
<link href="{% sass_src 'theme-dark.scss' %}" rel="stylesheet" type="text/css" <link href="{% sass_src 'theme-dark.scss' %}" rel="stylesheet" type="text/css"
media="(prefers-color-scheme: dark)"/> media="(prefers-color-scheme: dark)"/>
<link href="{% sass_src 'theme-light.scss' %}" rel="stylesheet" type="text/css" <link href="{% sass_src 'theme-light.scss' %}" rel="stylesheet" type="text/css"
media="(prefers-color-scheme: light)"/> media="(prefers-color-scheme: light)"/>
{% endif %} {% endif %}
</head> </head>
<body> <body>
<header> <header>
{% if has_toasts %} {% if has_toasts %}
<div class="toasts container grid-lg"> <div class="toasts container grid-lg">
<form action="{% url 'bookmarks:toasts.acknowledge' %}?return_url={{ request.path | urlencode }}" method="post"> <form action="{% url 'bookmarks:toasts.acknowledge' %}?return_url={{ request.path | urlencode }}" method="post">
{% csrf_token %} {% csrf_token %}
{% for toast in toast_messages %} {% for toast in toast_messages %}
<div class="toast"> <div class="toast">
{{ toast.message }} {{ toast.message }}
<button type="submit" name="toast" value="{{ toast.id }}" class="btn btn-clear float-right"></button> <button type="submit" name="toast" value="{{ toast.id }}" class="btn btn-clear float-right"></button>
</div> </div>
{% endfor %} {% endfor %}
</form> </form>
</div> </div>
{% endif %}
<div class="navbar container grid-lg">
<section class="navbar-section">
<a href="{% url 'bookmarks:index' %}" class="navbar-brand text-bold">
<img class="logo" src="{% static 'logo.png' %}" alt="Application logo">
<h1>linkding</h1>
</a>
</section>
{# Only show nav items menu when logged in #}
{% if request.user.is_authenticated %}
<section class="navbar-section">
{% include 'bookmarks/nav_menu.html' %}
</section>
{% endif %} {% endif %}
<div class="navbar container grid-lg"> </div>
<section class="navbar-section">
<a href="{% url 'bookmarks:index' %}" class="navbar-brand text-bold">
<img class="logo" src="{% static 'logo.png' %}" alt="Application logo">
<h1>linkding</h1>
</a>
</section>
{# Only show nav items menu when logged in #}
{% if request.user.is_authenticated %}
<section class="navbar-section">
{% include 'bookmarks/nav_menu.html' %}
</section>
{% endif %}
</div>
</header> </header>
<div class="content container grid-lg"> <div class="content container grid-lg">
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,93 +1,101 @@
{% load shared %}
{% htmlmin %}
{# Basic menu list #} {# Basic menu list #}
<div class="hide-md"> <div class="hide-md">
<a href="{% url 'bookmarks:new' %}" class="btn btn-primary mr-2">Add bookmark</a> <a href="{% url 'bookmarks:new' %}" class="btn btn-primary mr-2">Add bookmark</a>
<div class="dropdown"> <div class="dropdown">
<a href="#" class="btn btn-link dropdown-toggle" tabindex="0" style="padding-right: 0.2rem"> <a href="#" class="btn btn-link dropdown-toggle" tabindex="0" style="padding-right: 0.2rem">
Bookmarks Bookmarks
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" style="height:1rem;width:1rem;vertical-align: text-bottom;"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" /> style="height:1rem;width:1rem;vertical-align: text-bottom;">
</svg> <path fill-rule="evenodd"
</a> d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
<ul class="menu"> clip-rule="evenodd"/>
<li> </svg>
<a href="{% url 'bookmarks:index' %}" class="btn btn-link">Active</a> </a>
</li> <ul class="menu">
<li> <li>
<a href="{% url 'bookmarks:archived' %}" class="btn btn-link">Archived</a> <a href="{% url 'bookmarks:index' %}" class="btn btn-link">Active</a>
</li> </li>
{% if request.user.profile.enable_sharing %} <li>
<li> <a href="{% url 'bookmarks:archived' %}" class="btn btn-link">Archived</a>
<a href="{% url 'bookmarks:shared' %}" class="btn btn-link">Shared</a> </li>
</li> {% if request.user.profile.enable_sharing %}
{% endif %} <li>
<li> <a href="{% url 'bookmarks:shared' %}" class="btn btn-link">Shared</a>
<a href="{% url 'bookmarks:index' %}?q=!unread" class="btn btn-link">Unread</a> </li>
</li> {% endif %}
<li> <li>
<a href="{% url 'bookmarks:index' %}?q=!untagged" class="btn btn-link">Untagged</a> <a href="{% url 'bookmarks:index' %}?q=!unread" class="btn btn-link">Unread</a>
</li> </li>
</ul> <li>
</div> <a href="{% url 'bookmarks:index' %}?q=!untagged" class="btn btn-link">Untagged</a>
<a href="{% url 'bookmarks:settings.index' %}" class="btn btn-link">Settings</a> </li>
<a href="{% url 'logout' %}" class="btn btn-link">Logout</a> </ul>
</div>
<a href="{% url 'bookmarks:settings.index' %}" class="btn btn-link">Settings</a>
<a href="{% url 'logout' %}" class="btn btn-link">Logout</a>
</div> </div>
{# Menu drop-down for smaller devices #} {# Menu drop-down for smaller devices #}
<div class="show-md"> <div class="show-md">
<a href="{% url 'bookmarks:new' %}" class="btn btn-primary"> <a href="{% url 'bookmarks:new' %}" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="width: 24px; height: 24px"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" /> style="width: 24px; height: 24px">
</svg> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
</svg>
</a>
<div class="dropdown dropdown-right">
<a href="#" id="mobile-nav-menu-trigger" class="btn btn-link dropdown-toggle" tabindex="0">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
style="width: 24px; height: 24px">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</a> </a>
<div class="dropdown dropdown-right"> <!-- menu component -->
<a href="#" id="mobile-nav-menu-trigger" class="btn btn-link dropdown-toggle" tabindex="0"> <ul class="menu">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="width: 24px; height: 24px"> <li>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> <a href="{% url 'bookmarks:index' %}" class="btn btn-link">Bookmarks</a>
</svg> </li>
</a> <li style="padding-left: 1rem">
<!-- menu component --> <a href="{% url 'bookmarks:archived' %}" class="btn btn-link">Archived</a>
<ul class="menu"> </li>
<li> {% if request.user.profile.enable_sharing %}
<a href="{% url 'bookmarks:index' %}" class="btn btn-link">Bookmarks</a> <li style="padding-left: 1rem">
</li> <a href="{% url 'bookmarks:shared' %}" class="btn btn-link">Shared</a>
<li style="padding-left: 1rem"> </li>
<a href="{% url 'bookmarks:archived' %}" class="btn btn-link">Archived</a> {% endif %}
</li> <li style="padding-left: 1rem">
{% if request.user.profile.enable_sharing %} <a href="{% url 'bookmarks:index' %}?q=!unread" class="btn btn-link">Unread</a>
<li style="padding-left: 1rem"> </li>
<a href="{% url 'bookmarks:shared' %}" class="btn btn-link">Shared</a> <li style="padding-left: 1rem">
</li> <a href="{% url 'bookmarks:index' %}?q=!untagged" class="btn btn-link">Untagged</a>
{% endif %} </li>
<li style="padding-left: 1rem"> <li>
<a href="{% url 'bookmarks:index' %}?q=!unread" class="btn btn-link">Unread</a> <a href="{% url 'bookmarks:settings.index' %}" class="btn btn-link">Settings</a>
</li> </li>
<li style="padding-left: 1rem"> <li>
<a href="{% url 'bookmarks:index' %}?q=!untagged" class="btn btn-link">Untagged</a> <a href="{% url 'logout' %}" class="btn btn-link">Logout</a>
</li> </li>
<li> </ul>
<a href="{% url 'bookmarks:settings.index' %}" class="btn btn-link">Settings</a> </div>
</li>
<li>
<a href="{% url 'logout' %}" class="btn btn-link">Logout</a>
</li>
</ul>
</div>
</div> </div>
{% endhtmlmin %}
<script> <script>
// Hide mobile menu on outside click // Hide mobile menu on outside click
// The Spectre CSS component relies on focus changes to show/hide the dropdown, however mobile browsers like // The Spectre CSS component relies on focus changes to show/hide the dropdown, however mobile browsers like
// Safari will not trigger a blur event when clicking on a non-focusable element, so we have to simulate the // Safari will not trigger a blur event when clicking on a non-focusable element, so we have to simulate the
// behaviour through Javascript // behaviour through Javascript
const mobileNavMenuTrigger = document.getElementById('mobile-nav-menu-trigger'); const mobileNavMenuTrigger = document.getElementById('mobile-nav-menu-trigger');
function mobileNavMenuOutsideClickHandler(clickEvent) { function mobileNavMenuOutsideClickHandler(clickEvent) {
if (mobileNavMenuTrigger.parentElement.contains(clickEvent.target)) return if (mobileNavMenuTrigger.parentElement.contains(clickEvent.target)) return
mobileNavMenuTrigger.blur(); mobileNavMenuTrigger.blur();
} }
mobileNavMenuTrigger.addEventListener('focus', function () { mobileNavMenuTrigger.addEventListener('focus', function () {
document.addEventListener('click', mobileNavMenuOutsideClickHandler); document.addEventListener('click', mobileNavMenuOutsideClickHandler);
}) })
mobileNavMenuTrigger.addEventListener('blur', function () { mobileNavMenuTrigger.addEventListener('blur', function () {
document.removeEventListener('click', mobileNavMenuOutsideClickHandler); document.removeEventListener('click', mobileNavMenuOutsideClickHandler);
}) })
</script> </script>

View File

@@ -2,14 +2,14 @@
{% load bookmarks %} {% load bookmarks %}
{% block content %} {% block content %}
<div class="columns"> <div class="columns">
<section class="content-area column col-12"> <section class="content-area column col-12">
<div class="content-area-header"> <div class="content-area-header">
<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 return_url auto_close=auto_close %} {% bookmark_form form return_url auto_close=auto_close %}
</form> </form>
</section> </section>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,35 +1,35 @@
{% load shared %} {% load shared %}
<ul class="pagination"> <ul class="pagination">
{% if page.has_previous %} {% if page.has_previous %}
<li class="page-item"> <li class="page-item">
<a href="?{% update_query_string page=page.previous_page_number %}" tabindex="-1">Previous</a> <a href="?{% update_query_string page=page.previous_page_number %}" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
<a href="#" tabindex="-1">Previous</a> <a href="#" tabindex="-1">Previous</a>
</li> </li>
{% endif %} {% endif %}
{% for page_number in visible_page_numbers %} {% for page_number in visible_page_numbers %}
{% if page_number >= 0 %} {% if page_number >= 0 %}
<li class="page-item {% if page.number == page_number %}active{% endif %}"> <li class="page-item {% if page.number == page_number %}active{% endif %}">
<a href="?{% update_query_string page=page_number %}">{{ page_number }}</a> <a href="?{% update_query_string page=page_number %}">{{ page_number }}</a>
</li> </li>
{% else %}
<li class="page-item">
<span>...</span>
</li>
{% endif %}
{% endfor %}
{% if page.has_next %}
<li class="page-item">
<a href="?{% update_query_string page=page.next_page_number %}" tabindex="-1">Next</a>
</li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item">
<a href="#" tabindex="-1">Next</a> <span>...</span>
</li> </li>
{% endif %} {% endif %}
{% endfor %}
{% if page.has_next %}
<li class="page-item">
<a href="?{% update_query_string page=page.next_page_number %}" tabindex="-1">Next</a>
</li>
{% else %}
<li class="page-item disabled">
<a href="#" tabindex="-1">Next</a>
</li>
{% endif %}
</ul> </ul>

View File

@@ -1,43 +1,43 @@
<div class="search"> <div class="search">
<form action="" method="get" role="search"> <form action="" method="get" role="search">
<div class="input-group"> <div class="input-group">
<span id="search-input-wrap"> <span id="search-input-wrap">
<input type="search" class="form-input" name="q" placeholder="Search for words or #tags" <input type="search" class="form-input" name="q" placeholder="Search for words or #tags"
value="{{ filters.query }}"> value="{{ filters.query }}">
</span> </span>
<input type="submit" value="Search" class="btn input-group-btn"> <input type="submit" value="Search" class="btn input-group-btn">
</div> </div>
{% if filters.user %} {% if filters.user %}
<input type="hidden" name="user" value="{{ filters.user }}"> <input type="hidden" name="user" value="{{ filters.user }}">
{% endif %} {% endif %}
</form> </form>
</div> </div>
{# Replace search input with auto-complete component #} {# Replace search input with auto-complete component #}
<script type="application/javascript"> <script type="application/javascript">
window.addEventListener("load", function() { window.addEventListener("load", function () {
const currentTagsString = '{{ tags_string }}'; const currentTagsString = '{{ tags_string }}';
const currentTags = currentTagsString.split(' '); const currentTags = currentTagsString.split(' ');
const uniqueTags = [...new Set(currentTags)] const uniqueTags = [...new Set(currentTags)]
const filters = { const filters = {
q: '{{ filters.query }}', q: '{{ filters.query }}',
user: '{{ filters.user }}', user: '{{ filters.user }}',
} }
const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}') const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}')
const wrapper = document.getElementById('search-input-wrap') const wrapper = document.getElementById('search-input-wrap')
const newWrapper = document.createElement('div') const newWrapper = document.createElement('div')
new linkding.SearchAutoComplete({ new linkding.SearchAutoComplete({
target: newWrapper, target: newWrapper,
props: { props: {
name: 'q', name: 'q',
placeholder: 'Search for words or #tags', placeholder: 'Search for words or #tags',
value: '{{ filters.query }}', value: '{{ filters.query }}',
tags: uniqueTags, tags: uniqueTags,
mode: '{{ mode }}', mode: '{{ mode }}',
apiClient, apiClient,
filters, filters,
} }
}) })
wrapper.parentElement.replaceChild(newWrapper, wrapper) wrapper.parentElement.replaceChild(newWrapper, wrapper)
}); });
</script> </script>

View File

@@ -5,44 +5,44 @@
{% block content %} {% block content %}
<div class="bookmarks-page columns"> <div class="bookmarks-page columns">
{# Bookmark list #} {# Bookmark list #}
<section class="content-area column col-8 col-md-12"> <section class="content-area column col-8 col-md-12">
<div class="content-area-header"> <div class="content-area-header">
<h2>Shared bookmarks</h2> <h2>Shared bookmarks</h2>
<div class="spacer"></div> <div class="spacer"></div>
{% bookmark_search filters tags mode='shared' %} {% bookmark_search filters tags mode='shared' %}
</div> </div>
<form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ return_url }}" <form class="bookmark-actions" action="{% url 'bookmarks:action' %}?return_url={{ return_url }}"
method="post"> method="post">
{% csrf_token %} {% csrf_token %}
{% if empty %} {% if empty %}
{% include 'bookmarks/empty_bookmarks.html' %} {% include 'bookmarks/empty_bookmarks.html' %}
{% else %} {% else %}
{% bookmark_list bookmarks return_url link_target %} {% bookmark_list bookmarks return_url link_target %}
{% endif %} {% endif %}
</form> </form>
</section> </section>
{# Filters #} {# Filters #}
<section class="content-area column col-4 hide-md"> <section class="content-area column col-4 hide-md">
<div class="content-area-header"> <div class="content-area-header">
<h2>User</h2> <h2>User</h2>
</div> </div>
<div> <div>
{% user_select filters users %} {% user_select filters users %}
<br> <br>
</div> </div>
<div class="content-area-header"> <div class="content-area-header">
<h2>Tags</h2> <h2>Tags</h2>
</div> </div>
{% tag_cloud tags selected_tags %} {% tag_cloud tags selected_tags %}
</section> </section>
</div> </div>
<script src="{% static "bundle.js" %}"></script> <script src="{% static "bundle.js" %}"></script>
<script src="{% static "shared.js" %}"></script> <script src="{% static "shared.js" %}"></script>
{% endblock %} {% endblock %}

View File

@@ -1,35 +1,37 @@
{% load shared %} {% load shared %}
{% htmlmin %}
<div class="tag-cloud"> <div class="tag-cloud">
{% if has_selected_tags %} {% if has_selected_tags %}
<p class="selected-tags"> <p class="selected-tags">
{% for tag in selected_tags %} {% for tag in selected_tags %}
<a href="?{% remove_from_query_param q=tag.name|hash_tag %}" <a href="?{% remove_from_query_param q=tag.name|hash_tag %}"
class="text-bold mr-2"> class="text-bold mr-2">
<span>-{{ tag.name }}</span> <span>-{{ tag.name }}</span>
</a> </a>
{% endfor %} {% endfor %}
</p> </p>
{% endif %} {% endif %}
<div class="unselected-tags"> <div class="unselected-tags">
{% for group in groups %} {% for group in groups %}
<p class="group"> <p class="group">
{% for tag in group.tags %} {% for tag in group.tags %}
{# Highlight first char of first tag in group #} {# Highlight first char of first tag in group #}
{% if forloop.counter == 1 %} {% if forloop.counter == 1 %}
<a href="?{% append_to_query_param q=tag.name|hash_tag %}" <a href="?{% append_to_query_param q=tag.name|hash_tag %}"
class="mr-2" data-is-tag-item> class="mr-2" data-is-tag-item>
<span class="highlight-char">{{ tag.name|first_char }}</span><span>{{ tag.name|remaining_chars:1 }}</span> <span
</a> class="highlight-char">{{ tag.name|first_char }}</span><span>{{ tag.name|remaining_chars:1 }}</span>
{% else %} </a>
{# Render remaining tags normally #} {% else %}
<a href="?{% append_to_query_param q=tag.name|hash_tag %}" {# Render remaining tags normally #}
class="mr-2" data-is-tag-item> <a href="?{% append_to_query_param q=tag.name|hash_tag %}"
<span>{{ tag.name }}</span> class="mr-2" data-is-tag-item>
</a> <span>{{ tag.name }}</span>
{% endif %} </a>
{% endfor %} {% endif %}
</p> {% endfor %}
{% endfor %} </p>
{% endfor %}
</div> </div>
</div> </div>
{% endhtmlmin %}

View File

@@ -1,3 +1,5 @@
import re
from django import template from django import template
from bookmarks import utils from bookmarks import utils
@@ -48,6 +50,7 @@ def remove_from_query_param(context, **kwargs):
return query.urlencode() return query.urlencode()
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def replace_query_param(context, **kwargs): def replace_query_param(context, **kwargs):
query = context.request.GET.copy() query = context.request.GET.copy()
@@ -87,3 +90,22 @@ def humanize_relative_date(value):
if value in (None, ''): if value in (None, ''):
return '' return ''
return utils.humanize_relative_date(value) return utils.humanize_relative_date(value)
@register.tag
def htmlmin(parser, token):
nodelist = parser.parse(('endhtmlmin',))
parser.delete_first_token()
return HtmlMinNode(nodelist)
class HtmlMinNode(template.Node):
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context):
output = self.nodelist.render(context)
output = re.sub(r'\s+', ' ', output)
return output