diff --git a/CHANGELOG.md b/CHANGELOG.md index 11fc7a4f..399505ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## v1.31.0 (16/06/2024) + +### What's Changed +* Add support for bookmark thumbnails by @vslinko in https://github.com/sissbruecker/linkding/pull/721 +* Automatically add tags to bookmarks based on URL pattern by @vslinko in https://github.com/sissbruecker/linkding/pull/736 +* Load bookmark thumbnails after import by @vslinko in https://github.com/sissbruecker/linkding/pull/724 +* Load missing thumbnails after enabling the feature by @sissbruecker in https://github.com/sissbruecker/linkding/pull/725 +* Thumbnails lazy loading by @vslinko in https://github.com/sissbruecker/linkding/pull/734 +* Add option for disabling tag grouping by @vslinko in https://github.com/sissbruecker/linkding/pull/735 +* Preview auto tags in bookmark form by @sissbruecker in https://github.com/sissbruecker/linkding/pull/737 +* Hide tooltip on mobile by @vslinko in https://github.com/sissbruecker/linkding/pull/733 +* Bump requests from 2.31.0 to 2.32.0 by @dependabot in https://github.com/sissbruecker/linkding/pull/740 + +### New Contributors +* @vslinko made their first contribution in https://github.com/sissbruecker/linkding/pull/721 + +**Full Changelog**: https://github.com/sissbruecker/linkding/compare/v1.30.0...v1.31.0 + +--- + ## v1.30.0 (20/04/2024) ### What's Changed diff --git a/bookmarks/api/routes.py b/bookmarks/api/routes.py index 41f73219..5249c512 100644 --- a/bookmarks/api/routes.py +++ b/bookmarks/api/routes.py @@ -11,6 +11,7 @@ UserProfileSerializer, ) from bookmarks.models import Bookmark, BookmarkSearch, Tag, User +from bookmarks.services import auto_tagging from bookmarks.services.bookmarks import ( archive_bookmark, unarchive_bookmark, @@ -107,8 +108,18 @@ def check(self, request): else: metadata = website_loader.load_website_metadata(url) + # Return tags that would be automatically applied to the bookmark + profile = request.user.profile + auto_tags = [] + if profile.auto_tagging_rules: + auto_tags = auto_tagging.get_tags(profile.auto_tagging_rules, url) + return Response( - {"bookmark": existing_bookmark_data, "metadata": metadata.to_dict()}, + { + "bookmark": existing_bookmark_data, + "metadata": metadata.to_dict(), + "auto_tags": auto_tags, + }, status=status.HTTP_200_OK, ) diff --git a/bookmarks/e2e/e2e_test_bookmark_form.py b/bookmarks/e2e/e2e_test_bookmark_form.py index c4da421e..8c031d5d 100644 --- a/bookmarks/e2e/e2e_test_bookmark_form.py +++ b/bookmarks/e2e/e2e_test_bookmark_form.py @@ -85,3 +85,25 @@ def test_enter_url_of_existing_bookmark_should_show_notes(self): page.get_by_label("URL").fill(bookmark.url) expect(details).to_have_attribute("open", value="") + + def test_create_should_preview_auto_tags(self): + profile = self.get_or_create_test_user().profile + profile.auto_tagging_rules = "github.com dev github" + profile.save() + + with sync_playwright() as p: + # Open page with URL that should have auto tags + browser = self.setup_browser(p) + page = browser.new_page() + url = self.live_server_url + reverse("bookmarks:new") + url += f"?url=https%3A%2F%2Fgithub.com%2Fsissbruecker%2Flinkding" + page.goto(url) + + auto_tags_hint = page.locator(".form-input-hint.auto-tags") + expect(auto_tags_hint).to_be_visible() + expect(auto_tags_hint).to_have_text("Auto tags: dev github") + + # Change to URL without auto tags + page.get_by_label("URL").fill("https://example.com") + + expect(auto_tags_hint).to_be_hidden() diff --git a/bookmarks/management/commands/full_backup.py b/bookmarks/management/commands/full_backup.py index 49241564..642c89e1 100644 --- a/bookmarks/management/commands/full_backup.py +++ b/bookmarks/management/commands/full_backup.py @@ -48,6 +48,19 @@ def handle(self, *args, **options): file_path = os.path.join(root, file) zip_file.write(file_path, os.path.join("favicons", file)) + # Backup the previews folder + if not os.path.exists(os.path.join("data", "previews")): + self.stdout.write( + self.style.WARNING("No previews folder found. Skipping...") + ) + else: + self.stdout.write("Backup bookmark previews...") + previews_folder = os.path.join("data", "previews") + for root, _, files in os.walk(previews_folder): + for file in files: + file_path = os.path.join(root, file) + zip_file.write(file_path, os.path.join("previews", file)) + self.stdout.write(self.style.SUCCESS(f"Backup created at {backup_file}")) def backup_database(self, backup_db_file): diff --git a/bookmarks/styles/bookmark-form.scss b/bookmarks/styles/bookmark-form.scss index e80af60e..1b31754b 100644 --- a/bookmarks/styles/bookmark-form.scss +++ b/bookmarks/styles/bookmark-form.scss @@ -36,12 +36,11 @@ .form-input-hint.bookmark-exists { display: none; color: $warning-color; + } - a { - color: $warning-color; - text-decoration: underline; - font-weight: bold; - } + .form-input-hint.auto-tags { + display: none; + color: $success-color; } details.notes textarea { diff --git a/bookmarks/templates/bookmarks/form.html b/bookmarks/templates/bookmarks/form.html index 2dd05f80..d9beb61c 100644 --- a/bookmarks/templates/bookmarks/form.html +++ b/bookmarks/templates/bookmarks/form.html @@ -23,10 +23,10 @@ {{ form.tag_string|add_class:"form-input"|attr:"ld-tag-autocomplete"|attr:"autocomplete:off"|attr:"autocapitalize:off" }}
- Enter any number of tags separated by space and without the hash (#). If a tag does not - exist it will be - automatically created. + Enter any number of tags separated by space and without the hash (#). + If a tag does not exist it will be automatically created.
+
{{ form.tag_string.errors }}
@@ -197,6 +197,18 @@ } else { bookmarkExistsHint.style['display'] = 'none'; } + + // Preview auto tags + const autoTags = data.auto_tags; + const autoTagsHint = document.querySelector('.form-input-hint.auto-tags'); + + if (autoTags.length > 0) { + autoTags.sort(); + autoTagsHint.style['display'] = 'block'; + autoTagsHint.innerHTML = `Auto tags: ${autoTags.join(" ")}`; + } else { + autoTagsHint.style['display'] = 'none'; + } }); } diff --git a/bookmarks/tests/test_bookmarks_api.py b/bookmarks/tests/test_bookmarks_api.py index 4c58d2c1..58448df6 100644 --- a/bookmarks/tests/test_bookmarks_api.py +++ b/bookmarks/tests/test_bookmarks_api.py @@ -742,6 +742,34 @@ def test_check_returns_existing_metadata_if_url_is_bookmarked(self): self.assertEqual(bookmark.website_description, metadata["description"]) self.assertIsNone(metadata["preview_image"]) + def test_check_returns_no_auto_tags_if_none_configured(self): + self.authenticate() + + url = reverse("bookmarks:bookmark-check") + check_url = urllib.parse.quote_plus("https://example.com") + response = self.get( + f"{url}?url={check_url}", expected_status_code=status.HTTP_200_OK + ) + auto_tags = response.data["auto_tags"] + + self.assertCountEqual(auto_tags, []) + + def test_check_returns_matching_auto_tags(self): + self.authenticate() + + profile = self.get_or_create_test_user().profile + profile.auto_tagging_rules = "example.com tag1 tag2" + profile.save() + + url = reverse("bookmarks:bookmark-check") + check_url = urllib.parse.quote_plus("https://example.com") + response = self.get( + f"{url}?url={check_url}", expected_status_code=status.HTTP_200_OK + ) + auto_tags = response.data["auto_tags"] + + self.assertCountEqual(auto_tags, ["tag1", "tag2"]) + def test_can_only_access_own_bookmarks(self): self.authenticate() self.setup_bookmark() diff --git a/docs/backup.md b/docs/backup.md index e76e2308..05f32dd3 100644 --- a/docs/backup.md +++ b/docs/backup.md @@ -8,12 +8,13 @@ The data folder contains the following contents that are relevant for backups: - `db.sqlite3` - the SQLite database - `assets` - folder that contains HTML snapshots of bookmarks - `favicons` - folder that contains downloaded favicons +- `previews` - folder that contains downloaded preview images The following sections explain how to back up the individual contents. ## Full backup -linkding provides a CLI command to create a full backup of the data folder. This creates a zip file that contains backups of the database, assets, and favicons. +linkding provides a CLI command to create a full backup of the data folder. This creates a zip file that contains backups of the database, assets, favicons, and preview images. > [!NOTE] > This method assumes that you are using the default SQLite database. @@ -90,7 +91,7 @@ This is the least technical option to back up bookmarks, but has several limitat - It only exports your own bookmarks, not those of other users. - It does not export URLs of snapshots on the Internet Archive Wayback machine. - It does not export HTML snapshots of bookmarks. Even if you backup and restore the assets folder, the bookmarks will not be linked to the snapshots anymore. -- It does not export favicons. +- It does not export favicons or preview images. Only use this method if you are fine with the above limitations. diff --git a/package.json b/package.json index e080c8fb..067e1e76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "linkding", - "version": "1.30.1", + "version": "1.31.0", "description": "", "main": "index.js", "scripts": { diff --git a/requirements.txt b/requirements.txt index 57687741..65420b86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -61,6 +61,8 @@ pyopenssl==24.1.0 # via josepy python-dateutil==2.9.0.post0 # via -r requirements.in +pytz==2023.3.post1 + # via djangorestframework requests==2.32.3 # via # -r requirements.in diff --git a/version.txt b/version.txt index 034552a8..34aae156 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.30.0 +1.31.0