mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-14 05:59:29 +02:00
Archive snapshots of websites locally (#672)
* Add basic HTML snapshots * Implement asset list * Add snapshot creation tests * Add deletion tests * Show file size * Remove snapshots * Create new snapshots * Switch to single-file * CSS tweak * Remove auto refresh * Show delete link when there is no file yet * Add current date to display name * Add flag for snapshot support * Add option for disabling automatic snapshots * Make snapshots sharable * Document image variants * Update README.md * Add migrations * Fix tests
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
{% extends 'bookmarks/layout.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div ld-bookmark-details class="bookmark-details page">
|
||||
{% if request.user == bookmark.owner %}
|
||||
<div class="bookmark-details page">
|
||||
{% if details.is_editable %}
|
||||
{% include 'bookmarks/details/actions.html' %}
|
||||
{% endif %}
|
||||
{% include 'bookmarks/details/title.html' %}
|
||||
<div>
|
||||
{% include 'bookmarks/details/content.html' %}
|
||||
{% include 'bookmarks/details/form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@@ -1,11 +1,14 @@
|
||||
<div class="actions">
|
||||
<div class="left-actions">
|
||||
<a class="btn" href="{% url 'bookmarks:edit' bookmark.id %}?return_url={{ edit_return_url|urlencode }}">Edit</a>
|
||||
<a class="btn"
|
||||
href="{% url 'bookmarks:edit' details.bookmark.id %}?return_url={{ details.edit_return_url|urlencode }}">Edit</a>
|
||||
</div>
|
||||
<div class="right-actions">
|
||||
<form action="{% url 'bookmarks:index.action' %}?return_url={{ delete_return_url|urlencode }}" method="post">
|
||||
<form action="{% url 'bookmarks:index.action' %}?return_url={{ details.delete_return_url|urlencode }}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<button ld-confirm-button type="submit" name="remove" value="{{ bookmark.id }}" class="btn btn-link text-error">
|
||||
<button ld-confirm-button type="submit" name="remove" value="{{ details.bookmark.id }}"
|
||||
class="btn btn-link text-error">
|
||||
Delete...
|
||||
</button>
|
||||
</form>
|
||||
|
42
bookmarks/templates/bookmarks/details/asset_icon.html
Normal file
42
bookmarks/templates/bookmarks/details/asset_icon.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% if asset.content_type == 'text/html' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"/>
|
||||
<path d="M2 21v-6"/>
|
||||
<path d="M5 15v6"/>
|
||||
<path d="M2 18h3"/>
|
||||
<path d="M20 15v6h2"/>
|
||||
<path d="M13 21v-6l2 3l2 -3v6"/>
|
||||
<path d="M7.5 15h3"/>
|
||||
<path d="M9 15v6"/>
|
||||
</svg>
|
||||
{% elif asset.content_type == 'application/pdf' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M5 12v-7a2 2 0 0 1 2 -2h7l5 5v4"/>
|
||||
<path d="M5 18h1.5a1.5 1.5 0 0 0 0 -3h-1.5v6"/>
|
||||
<path d="M17 18h2"/>
|
||||
<path d="M20 15h-3v6"/>
|
||||
<path d="M11 15v6h1a2 2 0 0 0 2 -2v-2a2 2 0 0 0 -2 -2h-1z"/>
|
||||
</svg>
|
||||
{% elif asset.content_type == 'image/png' or asset.content_type == 'image/jpeg' or asset.content_type == 'image.gif' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M15 8h.01"/>
|
||||
<path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12z"/>
|
||||
<path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5"/>
|
||||
<path d="M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3"/>
|
||||
</svg>
|
||||
{% else %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M14 3v4a1 1 0 0 0 1 1h4"/>
|
||||
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"/>
|
||||
</svg>
|
||||
{% endif %}
|
37
bookmarks/templates/bookmarks/details/assets.html
Normal file
37
bookmarks/templates/bookmarks/details/assets.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% if details.assets %}
|
||||
<div class="assets">
|
||||
{% for asset in details.assets %}
|
||||
<div class="asset" data-asset-id="{{ asset.id }}">
|
||||
<div class="asset-icon {{ asset.icon_classes }}">
|
||||
{% include 'bookmarks/details/asset_icon.html' %}
|
||||
</div>
|
||||
<div class="asset-text truncate {{ asset.text_classes }}">
|
||||
<span>
|
||||
{{ asset.display_name }}
|
||||
{% if asset.status == 'pending' %}(queued){% endif %}
|
||||
{% if asset.status == 'failure' %}(failed){% endif %}
|
||||
</span>
|
||||
{% if asset.file_size %}
|
||||
<span class="filesize">{{ asset.file_size|filesizeformat }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="asset-actions">
|
||||
{% if asset.file %}
|
||||
<a class="btn btn-link" href="{% url 'bookmarks:assets.view' asset.id %}" target="_blank">View</a>
|
||||
{% endif %}
|
||||
{% if details.is_editable %}
|
||||
<button ld-confirm-button type="submit" name="remove_asset" value="{{ asset.id }}" class="btn btn-link">
|
||||
Remove
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if details.is_editable %}
|
||||
<div class="assets-actions">
|
||||
<button type="submit" name="create_snapshot" class="btn btn-link">Create HTML snapshot</button>
|
||||
</div>
|
||||
{% endif %}
|
@@ -1,85 +0,0 @@
|
||||
{% load static %}
|
||||
{% load shared %}
|
||||
|
||||
<div class="weblinks">
|
||||
<a class="weblink" href="{{ bookmark.url }}" rel="noopener"
|
||||
target="{{ request.user_profile.bookmark_link_target }}">
|
||||
{% if bookmark.favicon_file and request.user_profile.enable_favicons %}
|
||||
<img class="favicon" src="{% static bookmark.favicon_file %}" alt="">
|
||||
{% endif %}
|
||||
<span>{{ bookmark.url }}</span>
|
||||
</a>
|
||||
{% if bookmark.web_archive_snapshot_url %}
|
||||
<a class="weblink" href="{{ bookmark.web_archive_snapshot_url }}"
|
||||
target="{{ request.user_profile.bookmark_link_target }}">
|
||||
{% if bookmark.favicon_file and request.user_profile.enable_favicons %}
|
||||
<svg class="favicon" viewBox="0 0 76 86" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="m76 82v4h-76l.00080851-4zm-3-6v5h-70v-5zm-62.6696277-54 .8344146.4217275.4176066 6.7436084.4176065 10.9576581v10.5383496l-.4176065 13.1364492-.0694681 8.8498268-1.1825531.3523804h-4.17367003l-1.25202116-.3523804-.48627608-8.8498268-.41840503-13.0662957v-10.5375432l.41840503-11.028618.38167482-6.7798947.87034634-.3854412zm60.0004653 0 .8353798.4217275.4168913 6.7436084.4168913 10.9576581v10.5383496l-.4168913 13.1364492-.0686832 8.8498268-1.1835879.3523804h-4.1737047l-1.2522712-.3523804-.4879704-8.8498268-.4168913-13.0662957v-10.5375432l.4168913-11.028618.3833483-6.7798947.8697215-.3854412zm-42.000632 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2529447-.3523804-.4863246-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8688361-.3854412zm23 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2521462-.3523804-.4871231-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8696347-.3854412zm21.6697944-9v7h-70v-7zm-35.7200748-13 36.7200748 8.4088317-1.4720205 2.5911683h-70.32799254l-2.19998696-2.10140371z"
|
||||
fill="currentColor" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
<span>View on Internet Archive</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<dl class="grid columns-2 columns-sm-1 gap-0">
|
||||
{% if request.user == bookmark.owner %}
|
||||
<div class="status col-2">
|
||||
<dt>Status</dt>
|
||||
<dd class="d-flex" style="gap: .8rem">
|
||||
<form action="{% url 'bookmarks:details' bookmark.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" name="is_archived" {% if bookmark.is_archived %}checked{% endif %}>
|
||||
<i class="form-icon"></i> Archived
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" name="unread" {% if bookmark.unread %}checked{% endif %}>
|
||||
<i class="form-icon"></i> Unread
|
||||
</label>
|
||||
</div>
|
||||
{% if request.user_profile.enable_sharing %}
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" name="shared" {% if bookmark.shared %}checked{% endif %}>
|
||||
<i class="form-icon"></i> Shared
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if bookmark.tag_names %}
|
||||
<div class="tags col-1">
|
||||
<dt>Tags</dt>
|
||||
<dd>
|
||||
{% for tag_name in bookmark.tag_names %}
|
||||
<a href="{% url 'bookmarks:index' %}?{% add_tag_to_query tag_name %}">{{ tag_name|hash_tag }}</a>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="date-added col-1">
|
||||
<dt>Date added</dt>
|
||||
<dd>
|
||||
<span>{{ bookmark.date_added }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
{% if bookmark.resolved_description %}
|
||||
<div class="description col-2">
|
||||
<dt>Description</dt>
|
||||
<dd>{{ bookmark.resolved_description }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if bookmark.notes %}
|
||||
<div class="notes col-2">
|
||||
<dt>Notes</dt>
|
||||
<dd class="markdown">{% markdown bookmark.notes %}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
</dl>
|
99
bookmarks/templates/bookmarks/details/form.html
Normal file
99
bookmarks/templates/bookmarks/details/form.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% load static %}
|
||||
{% load shared %}
|
||||
|
||||
<form ld-form action="{% url 'bookmarks:details' details.bookmark.id %}"
|
||||
refresh-url="{% url 'bookmarks:partials.details_form' details.bookmark.id %}"
|
||||
refresh-events="bookmark-page-refresh"
|
||||
method="post">
|
||||
<div class="weblinks">
|
||||
<a class="weblink" href="{{ details.bookmark.url }}" rel="noopener"
|
||||
target="{{ details.profile.bookmark_link_target }}">
|
||||
{% if details.show_link_icons %}
|
||||
<img class="favicon" src="{% static details.bookmark.favicon_file %}" alt="">
|
||||
{% endif %}
|
||||
<span>{{ details.bookmark.url }}</span>
|
||||
</a>
|
||||
{% if details.bookmark.web_archive_snapshot_url %}
|
||||
<a class="weblink" href="{{ details.bookmark.web_archive_snapshot_url }}"
|
||||
target="{{ details.profile.bookmark_link_target }}">
|
||||
{% if details.show_link_icons %}
|
||||
<svg class="favicon" viewBox="0 0 76 86" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="m76 82v4h-76l.00080851-4zm-3-6v5h-70v-5zm-62.6696277-54 .8344146.4217275.4176066 6.7436084.4176065 10.9576581v10.5383496l-.4176065 13.1364492-.0694681 8.8498268-1.1825531.3523804h-4.17367003l-1.25202116-.3523804-.48627608-8.8498268-.41840503-13.0662957v-10.5375432l.41840503-11.028618.38167482-6.7798947.87034634-.3854412zm60.0004653 0 .8353798.4217275.4168913 6.7436084.4168913 10.9576581v10.5383496l-.4168913 13.1364492-.0686832 8.8498268-1.1835879.3523804h-4.1737047l-1.2522712-.3523804-.4879704-8.8498268-.4168913-13.0662957v-10.5375432l.4168913-11.028618.3833483-6.7798947.8697215-.3854412zm-42.000632 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2529447-.3523804-.4863246-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8688361-.3854412zm23 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2521462-.3523804-.4871231-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8696347-.3854412zm21.6697944-9v7h-70v-7zm-35.7200748-13 36.7200748 8.4088317-1.4720205 2.5911683h-70.32799254l-2.19998696-2.10140371z"
|
||||
fill="currentColor" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
{% endif %}
|
||||
<span>View on Internet Archive</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<dl class="grid columns-2 columns-sm-1 gap-0">
|
||||
{% if details.is_editable %}
|
||||
<div class="status col-2">
|
||||
<dt>Status</dt>
|
||||
<dd class="d-flex" style="gap: .8rem">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input ld-form-auto-submit type="checkbox" name="is_archived"
|
||||
{% if details.bookmark.is_archived %}checked{% endif %}>
|
||||
<i class="form-icon"></i> Archived
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input ld-form-auto-submit type="checkbox" name="unread"
|
||||
{% if details.bookmark.unread %}checked{% endif %}>
|
||||
<i class="form-icon"></i> Unread
|
||||
</label>
|
||||
</div>
|
||||
{% if details.profile.enable_sharing %}
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input ld-form-auto-submit type="checkbox" name="shared"
|
||||
{% if details.bookmark.shared %}checked{% endif %}>
|
||||
<i class="form-icon"></i> Shared
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if details.show_files %}
|
||||
<div class="files col-2">
|
||||
<dt>Files</dt>
|
||||
<dd>
|
||||
{% include 'bookmarks/details/assets.html' %}
|
||||
</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if details.bookmark.tag_names %}
|
||||
<div class="tags col-1">
|
||||
<dt>Tags</dt>
|
||||
<dd>
|
||||
{% for tag_name in details.bookmark.tag_names %}
|
||||
<a href="{% url 'bookmarks:index' %}?{% add_tag_to_query tag_name %}">{{ tag_name|hash_tag }}</a>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="date-added col-1">
|
||||
<dt>Date added</dt>
|
||||
<dd>
|
||||
<span>{{ details.bookmark.date_added }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
{% if details.bookmark.resolved_description %}
|
||||
<div class="description col-2">
|
||||
<dt>Description</dt>
|
||||
<dd>{{ details.bookmark.resolved_description }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if details.bookmark.notes %}
|
||||
<div class="notes col-2">
|
||||
<dt>Notes</dt>
|
||||
<dd class="markdown">{% markdown details.bookmark.notes %}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</form>
|
@@ -1,3 +1,3 @@
|
||||
<h2>
|
||||
{{ bookmark.resolved_title }}
|
||||
{{ details.bookmark.resolved_title }}
|
||||
</h2>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div ld-bookmark-details class="modal active bookmark-details">
|
||||
<div class="modal active bookmark-details">
|
||||
<div class="modal-overlay" aria-label="Close"></div>
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
@@ -14,11 +14,11 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="content">
|
||||
{% include 'bookmarks/details/content.html' %}
|
||||
{% include 'bookmarks/details/form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if request.user == bookmark.owner %}
|
||||
{% if details.is_editable %}
|
||||
<div class="modal-footer">
|
||||
{% include 'bookmarks/details/actions.html' %}
|
||||
</div>
|
||||
|
@@ -163,6 +163,18 @@
|
||||
href="{% url 'bookmarks:shared' %}">shared bookmarks page</a>.
|
||||
</div>
|
||||
</div>
|
||||
{% if has_snapshot_support %}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.enable_automatic_html_snapshots.id_for_label }}" class="form-checkbox">
|
||||
{{ form.enable_automatic_html_snapshots }}
|
||||
<i class="form-icon"></i> Automatically create HTML snapshots
|
||||
</label>
|
||||
<div class="form-input-hint">
|
||||
Automatically creates HTML snapshots when adding bookmarks. Alternatively, when disabled, snapshots can be
|
||||
created manually in the details view of a bookmark.
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<details {% if form.custom_css.value %}open{% endif %}>
|
||||
<summary>Custom CSS</summary>
|
||||
|
Reference in New Issue
Block a user