Compare commits

..

11 Commits

Author SHA1 Message Date
Sascha Ißbrücker
3eb8cfe45e Bump version 2022-03-25 18:55:58 +01:00
dependabot[bot]
f5b07eebba Bump sqlparse from 0.4.1 to 0.4.2 (#159)
Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.1 to 0.4.2.
- [Release notes](https://github.com/andialbrecht/sqlparse/releases)
- [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG)
- [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.1...0.4.2)

---
updated-dependencies:
- dependency-name: sqlparse
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-25 18:54:07 +01:00
dependabot[bot]
3ba8f7e30b Bump django from 3.2.6 to 3.2.12 (#197)
Bumps [django](https://github.com/django/django) from 3.2.6 to 3.2.12.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.6...3.2.12)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-25 18:51:59 +01:00
Sascha Ißbrücker
9a63c367a8 Move shortcut to docs 2022-03-25 18:35:05 +01:00
Sascha Ißbrücker
edb71286e7 Prevent external redirects 2022-03-25 18:29:54 +01:00
Sascha Ißbrücker
1ffc3e0266 Fix bookmark access restrictions 2022-03-22 02:24:21 +01:00
Sascha Ißbrücker
66995cfab2 Create SECURITY.md 2022-03-20 09:11:58 +01:00
Kazi
68143de992 How-to guide for HTTP Shortcuts app on Android (#201)
* How-to guide for HTTP Shortcuts app on Android

* refinements

* link fix
2022-02-28 08:12:37 +01:00
Fivefold
b93a9fadb6 Add linkding-injector community extension to README (#190) 2022-01-17 13:39:45 +01:00
Sascha Ißbrücker
77fea02f77 Update CHANGELOG.md 2021-12-13 00:12:51 +01:00
Sascha Ißbrücker
fcc0b6f591 Update CHANGELOG.md 2021-12-13 00:09:14 +01:00
20 changed files with 352 additions and 53 deletions

View File

@@ -1,5 +1,12 @@
# Changelog
## v1.8.5 (13/12/2021)
- [**bug**] Ensure tag names do not contain spaces [#182](https://github.com/sissbruecker/linkding/issues/182)
- [**bug**] Consider not copying whole GIT repository to Docker image [#174](https://github.com/sissbruecker/linkding/issues/174)
- [**enhancement**] Make bookmarks count column in admin sortable [#183](https://github.com/sissbruecker/linkding/pull/183)
---
## v1.8.4 (16/10/2021)
- [**enhancement**] Allow non-admin users to change their password [#166](https://github.com/sissbruecker/linkding/issues/166)

View File

@@ -169,3 +169,4 @@ The frontend is now available under http://localhost:8000
## Community
- [linkding-extension](https://github.com/jeroenpardon/linkding-extension) Chromium compatible extension that wraps the linkding bookmarklet. Tested with Chrome, Edge, Brave. By [jeroenpardon](https://github.com/jeroenpardon)
- [linkding-injector](https://github.com/Fivefold/linkding-injector) Injects search results from linkding into the sidebar of search pages like google and duckduckgo. Tested with Firefox and Chrome. By [Fivefold](https://github.com/Fivefold)

13
SECURITY.md Normal file
View File

@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.8.x | :white_check_mark: |
## Reporting a Vulnerability
To report a vulnerability, please send a mail to: 588ex5zl8@mozmail.com
I'll try to get back to you as soon as possible.

View File

@@ -99,12 +99,10 @@ class BookmarkForm(forms.ModelForm):
widget=forms.Textarea())
# Hidden field that determines whether to close window/tab after saving the bookmark
auto_close = forms.CharField(required=False)
# Hidden field that determines where to redirect after saving the form
return_url = forms.CharField(required=False)
class Meta:
model = Bookmark
fields = ['url', 'tag_string', 'title', 'description', 'auto_close', 'return_url']
fields = ['url', 'tag_string', 'title', 'description', 'auto_close']
class UserProfile(models.Model):

View File

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

View File

@@ -4,7 +4,6 @@
<div class="bookmarks-form">
{% csrf_token %}
{{ form.auto_close|attr:"type:hidden" }}
{{ form.return_url|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: " }}

View File

@@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
@@ -33,3 +34,22 @@ class BookmarkArchiveViewTestCase(TestCase, BookmarkFactoryMixin):
)
self.assertRedirects(response, reverse('bookmarks:close'))
def test_can_only_archive_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark = self.setup_bookmark(user=other_user)
response = self.client.get(reverse('bookmarks:archive', args=[bookmark.id]))
bookmark.refresh_from_db()
self.assertEqual(response.status_code, 404)
self.assertFalse(bookmark.is_archived)
def test_should_not_redirect_to_external_url(self):
bookmark = self.setup_bookmark()
response = self.client.get(
reverse('bookmarks:archive', args=[bookmark.id]) + '?return_url=https://example.com'
)
self.assertRedirects(response, reverse('bookmarks:index'))

View File

@@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django.forms import model_to_dict
from django.test import TestCase
from django.urls import reverse
@@ -32,6 +33,21 @@ class BookmarkBulkEditViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived)
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived)
def test_can_only_bulk_archive_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark1 = self.setup_bookmark(user=other_user)
bookmark2 = self.setup_bookmark(user=other_user)
bookmark3 = self.setup_bookmark(user=other_user)
self.client.post(reverse('bookmarks:bulk_edit'), {
'bulk_archive': [''],
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
})
self.assertFalse(Bookmark.objects.get(id=bookmark1.id).is_archived)
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived)
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived)
def test_bulk_unarchive(self):
bookmark1 = self.setup_bookmark(is_archived=True)
bookmark2 = self.setup_bookmark(is_archived=True)
@@ -46,6 +62,21 @@ class BookmarkBulkEditViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertFalse(Bookmark.objects.get(id=bookmark2.id).is_archived)
self.assertFalse(Bookmark.objects.get(id=bookmark3.id).is_archived)
def test_can_only_bulk_unarchive_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark1 = self.setup_bookmark(is_archived=True, user=other_user)
bookmark2 = self.setup_bookmark(is_archived=True, user=other_user)
bookmark3 = self.setup_bookmark(is_archived=True, user=other_user)
self.client.post(reverse('bookmarks:bulk_edit'), {
'bulk_unarchive': [''],
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
})
self.assertTrue(Bookmark.objects.get(id=bookmark1.id).is_archived)
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived)
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived)
def test_bulk_delete(self):
bookmark1 = self.setup_bookmark()
bookmark2 = self.setup_bookmark()
@@ -57,8 +88,23 @@ class BookmarkBulkEditViewTestCase(TestCase, BookmarkFactoryMixin):
})
self.assertIsNone(Bookmark.objects.filter(id=bookmark1.id).first())
self.assertFalse(Bookmark.objects.filter(id=bookmark2.id).first())
self.assertFalse(Bookmark.objects.filter(id=bookmark3.id).first())
self.assertIsNone(Bookmark.objects.filter(id=bookmark2.id).first())
self.assertIsNone(Bookmark.objects.filter(id=bookmark3.id).first())
def test_can_only_bulk_delete_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark1 = self.setup_bookmark(user=other_user)
bookmark2 = self.setup_bookmark(user=other_user)
bookmark3 = self.setup_bookmark(user=other_user)
self.client.post(reverse('bookmarks:bulk_edit'), {
'bulk_delete': [''],
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
})
self.assertIsNotNone(Bookmark.objects.filter(id=bookmark1.id).first())
self.assertIsNotNone(Bookmark.objects.filter(id=bookmark2.id).first())
self.assertIsNotNone(Bookmark.objects.filter(id=bookmark3.id).first())
def test_bulk_tag(self):
bookmark1 = self.setup_bookmark()
@@ -81,6 +127,28 @@ class BookmarkBulkEditViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertCountEqual(bookmark2.tags.all(), [tag1, tag2])
self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2])
def test_can_only_bulk_tag_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark1 = self.setup_bookmark(user=other_user)
bookmark2 = self.setup_bookmark(user=other_user)
bookmark3 = self.setup_bookmark(user=other_user)
tag1 = self.setup_tag()
tag2 = self.setup_tag()
self.client.post(reverse('bookmarks:bulk_edit'), {
'bulk_tag': [''],
'bulk_tag_string': [f'{tag1.name} {tag2.name}'],
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
})
bookmark1.refresh_from_db()
bookmark2.refresh_from_db()
bookmark3.refresh_from_db()
self.assertCountEqual(bookmark1.tags.all(), [])
self.assertCountEqual(bookmark2.tags.all(), [])
self.assertCountEqual(bookmark3.tags.all(), [])
def test_bulk_untag(self):
tag1 = self.setup_tag()
tag2 = self.setup_tag()
@@ -102,6 +170,28 @@ class BookmarkBulkEditViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertCountEqual(bookmark2.tags.all(), [])
self.assertCountEqual(bookmark3.tags.all(), [])
def test_can_only_bulk_untag_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
tag1 = self.setup_tag()
tag2 = self.setup_tag()
bookmark1 = self.setup_bookmark(tags=[tag1, tag2], user=other_user)
bookmark2 = self.setup_bookmark(tags=[tag1, tag2], user=other_user)
bookmark3 = self.setup_bookmark(tags=[tag1, tag2], user=other_user)
self.client.post(reverse('bookmarks:bulk_edit'), {
'bulk_untag': [''],
'bulk_tag_string': [f'{tag1.name} {tag2.name}'],
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
})
bookmark1.refresh_from_db()
bookmark2.refresh_from_db()
bookmark3.refresh_from_db()
self.assertCountEqual(bookmark1.tags.all(), [tag1, tag2])
self.assertCountEqual(bookmark2.tags.all(), [tag1, tag2])
self.assertCountEqual(bookmark3.tags.all(), [tag1, tag2])
def test_bulk_edit_handles_empty_bookmark_id(self):
bookmark1 = self.setup_bookmark()
bookmark2 = self.setup_bookmark()
@@ -130,3 +220,29 @@ class BookmarkBulkEditViewTestCase(TestCase, BookmarkFactoryMixin):
})
self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3])
def test_bulk_edit_should_redirect_to_return_url(self):
bookmark1 = self.setup_bookmark()
bookmark2 = self.setup_bookmark()
bookmark3 = self.setup_bookmark()
url = reverse('bookmarks:bulk_edit') + '?return_url=' + reverse('bookmarks:settings.index')
response = self.client.post(url, {
'bulk_archive': [''],
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
})
self.assertRedirects(response, reverse('bookmarks:settings.index'))
def test_bulk_edit_should_not_redirect_to_external_url(self):
bookmark1 = self.setup_bookmark()
bookmark2 = self.setup_bookmark()
bookmark3 = self.setup_bookmark()
url = reverse('bookmarks:bulk_edit') + '?return_url=https://example.com'
response = self.client.post(url, {
'bulk_archive': [''],
'bookmark_id': [str(bookmark1.id), str(bookmark2.id), str(bookmark3.id)],
})
self.assertRedirects(response, reverse('bookmarks:index'))

View File

@@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
@@ -19,7 +20,6 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
'tag_string': 'editedtag1 editedtag2',
'title': 'edited title',
'description': 'edited description',
'return_url': reverse('bookmarks:index'),
}
return {**form_data, **overrides}
@@ -39,17 +39,6 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertEqual(bookmark.tags.all()[0].name, 'editedtag1')
self.assertEqual(bookmark.tags.all()[1].name, 'editedtag2')
def test_should_use_bookmark_index_as_default_return_url(self):
bookmark = self.setup_bookmark()
response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id]))
html = response.content.decode()
self.assertInHTML(
'<input type="hidden" name="return_url" value="{0}" '
'id="id_return_url">'.format(reverse('bookmarks:index')),
html)
def test_should_prefill_bookmark_form_fields(self):
tag1 = self.setup_tag()
tag2 = self.setup_tag()
@@ -80,18 +69,38 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
'</textarea>'.format(bookmark.description),
html)
def test_should_prefill_return_url_from_url_parameter(self):
bookmark = self.setup_bookmark()
response = self.client.get(reverse('bookmarks:edit', args=[bookmark.id]) + '?return_url=/test-return-url')
html = response.content.decode()
self.assertInHTML('<input type="hidden" name="return_url" value="/test-return-url" id="id_return_url">', html)
def test_should_redirect_to_return_url(self):
bookmark = self.setup_bookmark()
form_data = self.create_form_data({'return_url': reverse('bookmarks:close')})
form_data = self.create_form_data()
url = reverse('bookmarks:edit', args=[bookmark.id]) + '?return_url=' + reverse('bookmarks:close')
response = self.client.post(url, form_data)
self.assertRedirects(response, reverse('bookmarks:close'))
def test_should_redirect_to_bookmark_index_by_default(self):
bookmark = self.setup_bookmark()
form_data = self.create_form_data()
response = self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data)
self.assertRedirects(response, form_data['return_url'])
self.assertRedirects(response, reverse('bookmarks:index'))
def test_should_not_redirect_to_external_url(self):
bookmark = self.setup_bookmark()
form_data = self.create_form_data({'return_url': 'https://example.com'})
response = self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data)
self.assertRedirects(response, reverse('bookmarks:index'))
def test_can_only_edit_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark = self.setup_bookmark(user=other_user)
form_data = self.create_form_data({'id': bookmark.id})
response = self.client.post(reverse('bookmarks:edit', args=[bookmark.id]), form_data)
bookmark.refresh_from_db()
self.assertNotEqual(bookmark.url, form_data['url'])
self.assertEqual(response.status_code, 404)

View File

@@ -73,6 +73,13 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertRedirects(response, reverse('bookmarks:index'))
def test_should_not_redirect_to_external_url(self):
form_data = self.create_form_data()
response = self.client.post(reverse('bookmarks:new') + '?return_url=https://example.com', form_data)
self.assertRedirects(response, reverse('bookmarks:index'))
def test_auto_close_should_redirect_to_close_view(self):
form_data = self.create_form_data({'auto_close': 'true'})

View File

@@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
@@ -33,3 +34,21 @@ class BookmarkRemoveViewTestCase(TestCase, BookmarkFactoryMixin):
)
self.assertRedirects(response, reverse('bookmarks:close'))
def test_should_not_redirect_to_external_url(self):
bookmark = self.setup_bookmark()
response = self.client.get(
reverse('bookmarks:remove', args=[bookmark.id]) + '?return_url=https://example.com'
)
self.assertRedirects(response, reverse('bookmarks:index'))
def test_can_only_edit_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark = self.setup_bookmark(user=other_user)
response = self.client.get(reverse('bookmarks:remove', args=[bookmark.id]))
self.assertEqual(response.status_code, 404)
self.assertTrue(Bookmark.objects.filter(id=bookmark.id).exists())

View File

@@ -1,3 +1,4 @@
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
@@ -33,3 +34,22 @@ class BookmarkUnarchiveViewTestCase(TestCase, BookmarkFactoryMixin):
)
self.assertRedirects(response, reverse('bookmarks:close'))
def test_should_not_redirect_to_external_url(self):
bookmark = self.setup_bookmark()
response = self.client.get(
reverse('bookmarks:unarchive', args=[bookmark.id]) + '?return_url=https://example.com'
)
self.assertRedirects(response, reverse('bookmarks:archived'))
def test_can_only_archive_own_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
bookmark = self.setup_bookmark(is_archived=True, user=other_user)
response = self.client.get(reverse('bookmarks:unarchive', args=[bookmark.id]))
bookmark.refresh_from_db()
self.assertEqual(response.status_code, 404)
self.assertTrue(bookmark.is_archived)

View File

@@ -95,3 +95,10 @@ def parse_timestamp(value: str):
# Timestamp is out of range
raise ValueError(f'{value} exceeds maximum value for a timestamp')
def get_safe_return_url(return_url: str, fallback_url: str):
# Use fallback if URL is none or URL is not on same domain
if not return_url or not return_url.startswith('/'):
return fallback_url
return return_url

View File

@@ -2,7 +2,7 @@ import urllib.parse
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render
from django.urls import reverse
@@ -10,6 +10,7 @@ from bookmarks import queries
from bookmarks.models import Bookmark, BookmarkForm, build_tag_string
from bookmarks.services.bookmarks import create_bookmark, update_bookmark, archive_bookmark, archive_bookmarks, \
unarchive_bookmark, unarchive_bookmarks, delete_bookmarks, tag_bookmarks, untag_bookmarks
from bookmarks.utils import get_safe_return_url
_default_page_size = 30
@@ -108,23 +109,22 @@ def new(request):
@login_required
def edit(request, bookmark_id: int):
bookmark = Bookmark.objects.get(pk=bookmark_id)
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
if request.method == 'POST':
form = BookmarkForm(request.POST, instance=bookmark)
return_url = form.data['return_url']
if form.is_valid():
tag_string = convert_tag_string(form.data['tag_string'])
update_bookmark(form.save(commit=False), tag_string, request.user)
return HttpResponseRedirect(return_url)
else:
return_url = request.GET.get('return_url')
form = BookmarkForm(instance=bookmark)
return_url = return_url if return_url else reverse('bookmarks:index')
form.initial['tag_string'] = build_tag_string(bookmark.tag_names, ' ')
form.initial['return_url'] = return_url
context = {
'form': form,
@@ -137,28 +137,37 @@ def edit(request, bookmark_id: int):
@login_required
def remove(request, bookmark_id: int):
bookmark = Bookmark.objects.get(pk=bookmark_id)
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
bookmark.delete()
return_url = request.GET.get('return_url')
return_url = return_url if return_url else reverse('bookmarks:index')
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
return HttpResponseRedirect(return_url)
@login_required
def archive(request, bookmark_id: int):
bookmark = Bookmark.objects.get(pk=bookmark_id)
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
archive_bookmark(bookmark)
return_url = request.GET.get('return_url')
return_url = return_url if return_url else reverse('bookmarks:index')
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
return HttpResponseRedirect(return_url)
@login_required
def unarchive(request, bookmark_id: int):
bookmark = Bookmark.objects.get(pk=bookmark_id)
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
unarchive_bookmark(bookmark)
return_url = request.GET.get('return_url')
return_url = return_url if return_url else reverse('bookmarks:archived')
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:archived'))
return HttpResponseRedirect(return_url)
@@ -180,8 +189,7 @@ def bulk_edit(request):
tag_string = convert_tag_string(request.POST['bulk_tag_string'])
untag_bookmarks(bookmark_ids, tag_string, request.user)
return_url = request.GET.get('return_url')
return_url = return_url if return_url else reverse('bookmarks:index')
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
return HttpResponseRedirect(return_url)

View File

@@ -20,6 +20,22 @@ Now when you are browsing the web and you want to save the current page as a boo
For more info see here: https://paul.kinlan.me/use-bookmarklets-on-chrome-on-android/
## Using HTTP Shortcuts app on Android
**Note** This allows you to share URL from any app to bookmark it to linkding
- Install HTTP Shortcuts from [Play Store](https://play.google.com/store/apps/details?id=ch.rmy.android.http_shortcuts) or [F-Droid](https://f-droid.org/en/packages/ch.rmy.android.http_shortcuts/).
- Download [linkding_shortcut.json](/docs/linkding_shortcut.json) from this repository.
- Open HTTP Shortcuts, tap the 3-dot-button at the top-right corner, tap `Import/Export`, then tap `Import from file`.
- Select the json file you downloaded earlier, go back, tap the 3-dot-button again, then tap `Variables`.
- Edit the `values` of `linkding_instance`, `linkding_tag` and `linkding_api_token`.
Try using share button on an app, a new item `Send to...` should appear on the share sheet. You can also manually share by tapping the shortcut inside the HTTP Shortcuts app itself.
## Create a share action on iOS for adding bookmarks to linkding
This how-to explains how to make use of the app shortcuts iOS app to create a share action that can be used in Safari for adding bookmarks to your linkding instance.

View File

@@ -0,0 +1,59 @@
{
"categories": [
{
"id": "8f4299d4-4c30-4a8e-a3f9-c90694011713",
"name": "Shortcuts",
"shortcuts": [
{
"bodyContent": "{ \"url\": \"{{b2953f61-b302-4c79-b90d-39858a06d9a6}}\", \"tag_names\": [ \"{{c360f61f-ce17-47b4-bea3-1d8c3913ca52}}\" ] }",
"contentType": "application/json",
"description": "Bookmark to linkding",
"headers": [
{
"id": "d235f7b4-fce2-41f4-a00f-72d5fde9e4b9",
"key": "Authorization",
"value": "Token {{6a739a16-d16d-4a06-93a5-3457da3c3d20}}"
}
],
"iconName": "flat_grey_ribbon",
"id": "1e047d02-a4a3-4cad-b4cc-123cc16c8398",
"launcherShortcut": true,
"method": "POST",
"name": "Linkding",
"quickSettingsTileShortcut": true,
"responseHandling": {
"failureOutput": "simple",
"id": "61fa9fc3-8b7a-47ce-b43c-f24618a65e1e",
"uiType": "toast"
},
"url": "{{ea2db14b-b9ca-45d8-8555-403271a38f5a}}/api/bookmarks/"
}
]
}
],
"variables": [
{
"id": "ea2db14b-b9ca-45d8-8555-403271a38f5a",
"key": "linkding_instance",
"value": "https://your.instance.tld.without.slashed.end"
},
{
"flags": 1,
"id": "b2953f61-b302-4c79-b90d-39858a06d9a6",
"key": "linkding_add_url",
"title": "Enter URL",
"type": "text"
},
{
"id": "c360f61f-ce17-47b4-bea3-1d8c3913ca52",
"key": "linkding_tag",
"value": "single-tag"
},
{
"id": "6a739a16-d16d-4a06-93a5-3457da3c3d20",
"key": "linkding_api_token",
"value": "your_token_from_integrations_tab"
}
],
"version": 45
}

View File

@@ -1,6 +1,6 @@
{
"name": "linkding",
"version": "1.8.5",
"version": "1.8.6",
"description": "",
"main": "index.js",
"scripts": {

View File

@@ -3,7 +3,7 @@ beautifulsoup4==4.7.1
certifi==2019.6.16
charset-normalizer==2.0.4
confusable-homoglyphs==3.2.0
Django==3.2.6
Django==3.2.12
django-background-tasks==1.2.5
django-compat==1.0.15
django-generate-secret-key==1.0.2
@@ -18,7 +18,7 @@ python-dateutil==2.8.1
pytz==2021.1
requests==2.26.0
soupsieve==1.9.2
sqlparse==0.4.1
sqlparse==0.4.2
supervisor==4.2.2
typing-extensions==3.10.0.0
urllib3==1.26.6

View File

@@ -4,7 +4,7 @@ certifi==2019.6.16
charset-normalizer==2.0.4
confusable-homoglyphs==3.2.0
coverage==5.5
Django==3.2.6
Django==3.2.12
django-appconf==1.0.4
django-background-tasks==1.2.5
django-compat==1.0.15
@@ -26,7 +26,7 @@ requests==2.26.0
rjsmin==1.1.0
six==1.16.0
soupsieve==1.9.2
sqlparse==0.4.1
sqlparse==0.4.2
typing-extensions==3.10.0.0
urllib3==1.26.6
waybackpy==2.4.3

View File

@@ -1 +1 @@
1.8.5
1.8.6