diff --git a/.gitignore b/.gitignore index 2f6d9ed9..454fb7f4 100644 --- a/.gitignore +++ b/.gitignore @@ -192,7 +192,7 @@ typings/ # Database file /data # ublock + chromium -/uBlock0.chromium +/uBOLite.chromium.mv3 /chromium-profile /.direnv diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ececd38..14f297d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ # Changelog -## (23/09/2024) +## v1.36.0 (02/10/2024) + +### What's Changed +* Replace uBlock Origin with uBlock Origin Lite by @sissbruecker in https://github.com/sissbruecker/linkding/pull/866 +* Add LAST_MODIFIED attribute when exporting by @ixzhao in https://github.com/sissbruecker/linkding/pull/860 +* Return client error status code for invalid form submissions by @sissbruecker in https://github.com/sissbruecker/linkding/pull/849 +* Fix header.svg text by @vladh in https://github.com/sissbruecker/linkding/pull/850 +* Do not clear fields in POST requests (API behavior change) by @sissbruecker in https://github.com/sissbruecker/linkding/pull/852 +* Prevent duplicates when editing by @sissbruecker in https://github.com/sissbruecker/linkding/pull/853 +* Fix jumping details modal on back navigation by @sissbruecker in https://github.com/sissbruecker/linkding/pull/854 +* Fix select dropdown menu background in dark theme by @sissbruecker in https://github.com/sissbruecker/linkding/pull/858 +* Do not escape valid characters in custom CSS by @sissbruecker in https://github.com/sissbruecker/linkding/pull/863 +* Simplify Docker build by @sissbruecker in https://github.com/sissbruecker/linkding/pull/865 +* Improve error handling for auto tagging by @sissbruecker in https://github.com/sissbruecker/linkding/pull/855 +* Bump rollup from 4.13.0 to 4.22.4 by @dependabot in https://github.com/sissbruecker/linkding/pull/851 +* Bump rollup from 4.21.3 to 4.22.4 in /docs by @dependabot in https://github.com/sissbruecker/linkding/pull/856 + +### New Contributors +* @vladh made their first contribution in https://github.com/sissbruecker/linkding/pull/850 +* @ixzhao made their first contribution in https://github.com/sissbruecker/linkding/pull/860 + +**Full Changelog**: https://github.com/sissbruecker/linkding/compare/v1.35.0...v1.36.0 + +--- + +## v1.35.0 (23/09/2024) ### What's Changed * Add configuration options for pagination by @sissbruecker in https://github.com/sissbruecker/linkding/pull/835 diff --git a/bookmarks/migrations/0042_userprofile_custom_css_hash.py b/bookmarks/migrations/0042_userprofile_custom_css_hash.py new file mode 100644 index 00000000..bc027ac8 --- /dev/null +++ b/bookmarks/migrations/0042_userprofile_custom_css_hash.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.1 on 2024-09-28 08:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookmarks", "0041_merge_metadata"), + ] + + operations = [ + migrations.AddField( + model_name="userprofile", + name="custom_css_hash", + field=models.CharField(blank=True, max_length=32), + ), + ] diff --git a/bookmarks/models.py b/bookmarks/models.py index 2b94cfac..fe4530bb 100644 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -1,4 +1,5 @@ import binascii +import hashlib import logging import os from typing import List @@ -430,6 +431,7 @@ class UserProfile(models.Model): display_remove_bookmark_action = models.BooleanField(default=True, null=False) permanent_notes = models.BooleanField(default=False, null=False) custom_css = models.TextField(blank=True, null=False) + custom_css_hash = models.CharField(blank=True, null=False, max_length=32) auto_tagging_rules = models.TextField(blank=True, null=False) search_preferences = models.JSONField(default=dict, null=False) enable_automatic_html_snapshots = models.BooleanField(default=True, null=False) @@ -439,6 +441,15 @@ class UserProfile(models.Model): ) sticky_pagination = models.BooleanField(default=False, null=False) + def save(self, *args, **kwargs): + if self.custom_css: + self.custom_css_hash = hashlib.md5( + self.custom_css.encode("utf-8") + ).hexdigest() + else: + self.custom_css_hash = "" + super().save(*args, **kwargs) + class UserProfileForm(forms.ModelForm): class Meta: diff --git a/bookmarks/services/auto_tagging.py b/bookmarks/services/auto_tagging.py index 26087616..840745af 100644 --- a/bookmarks/services/auto_tagging.py +++ b/bookmarks/services/auto_tagging.py @@ -7,6 +7,9 @@ def get_tags(script: str, url: str): parsed_url = urlparse(url.lower()) result = set() + if not parsed_url.hostname: + return result + for line in script.lower().split("\n"): if "#" in line: i = line.index("#") diff --git a/bookmarks/services/exporter.py b/bookmarks/services/exporter.py index ebc3632b..fe41bf49 100644 --- a/bookmarks/services/exporter.py +++ b/bookmarks/services/exporter.py @@ -40,9 +40,10 @@ def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark): toread = "1" if bookmark.unread else "0" private = "0" if bookmark.shared else "1" added = int(bookmark.date_added.timestamp()) + modified = int(bookmark.date_modified.timestamp()) doc.append( - f'
{title}' + f'
{title}' ) if desc: diff --git a/bookmarks/services/importer.py b/bookmarks/services/importer.py index d4bbd5a9..41fda084 100644 --- a/bookmarks/services/importer.py +++ b/bookmarks/services/importer.py @@ -231,7 +231,10 @@ def _copy_bookmark_data( bookmark.date_added = parse_timestamp(netscape_bookmark.date_added) else: bookmark.date_added = timezone.now() - bookmark.date_modified = bookmark.date_added + if netscape_bookmark.date_modified: + bookmark.date_modified = parse_timestamp(netscape_bookmark.date_modified) + else: + bookmark.date_modified = bookmark.date_added bookmark.unread = netscape_bookmark.to_read if netscape_bookmark.title: bookmark.title = netscape_bookmark.title diff --git a/bookmarks/services/parser.py b/bookmarks/services/parser.py index 81c4a414..4eb7c1cb 100644 --- a/bookmarks/services/parser.py +++ b/bookmarks/services/parser.py @@ -12,6 +12,7 @@ class NetscapeBookmark: description: str notes: str date_added: str + date_modified: str tag_names: List[str] to_read: bool private: bool @@ -27,6 +28,7 @@ def __init__(self): self.bookmark = None self.href = "" self.add_date = "" + self.last_modified = "" self.tags = "" self.title = "" self.description = "" @@ -72,6 +74,7 @@ def handle_start_a(self, attrs: Dict[str, str]): description="", notes="", date_added=self.add_date, + date_modified=self.last_modified, tag_names=tag_names, to_read=self.toread == "1", # Mark as private by default, also when attribute is not specified @@ -97,6 +100,7 @@ def add_bookmark(self): self.bookmark = None self.href = "" self.add_date = "" + self.last_modified = "" self.tags = "" self.title = "" self.description = "" diff --git a/bookmarks/styles/theme-dark.css b/bookmarks/styles/theme-dark.css index 48585f21..bec8ca33 100644 --- a/bookmarks/styles/theme-dark.css +++ b/bookmarks/styles/theme-dark.css @@ -141,3 +141,10 @@ --bookmark-actions-weight: 400; --bulk-actions-bg-color: var(--contrast-5); } + +/* Try to force dark color scheme for all native elements (e.g. upload button +in file inputs, native select dropdown). For the select dropdown some browsers +ignore this and use whatever users have configured in their system settings. */ +:root { + color-scheme: dark; +} diff --git a/bookmarks/styles/theme/forms.css b/bookmarks/styles/theme/forms.css index e07f6f20..0a5b6aed 100644 --- a/bookmarks/styles/theme/forms.css +++ b/bookmarks/styles/theme/forms.css @@ -197,6 +197,16 @@ textarea.form-input { no-repeat right 0.35rem center / 0.4rem 0.5rem; padding-right: calc(var(--control-icon-size) + var(--control-padding-x)); } + + /* Options */ + & option { + /* On Windows with Chrome / Edge, options seems to use the same + background color as the select. However for the dark theme the + background is a semi-transparent white, resulting in an opaque white + background for the dropdown. Use the modal background color to force + a dark background instead. */ + background: var(--modal-container-bg-color); + } } /* Form element: Checkbox and Radio */ diff --git a/bookmarks/styles/theme/modals.css b/bookmarks/styles/theme/modals.css index 0077fd3b..aa0d73a8 100644 --- a/bookmarks/styles/theme/modals.css +++ b/bookmarks/styles/theme/modals.css @@ -33,7 +33,7 @@ cursor: default; display: block; left: 0; - position: absolute; + position: fixed; right: 0; top: 0; } diff --git a/bookmarks/templates/bookmarks/head.html b/bookmarks/templates/bookmarks/head.html index 61dd8fb6..b28a1caf 100644 --- a/bookmarks/templates/bookmarks/head.html +++ b/bookmarks/templates/bookmarks/head.html @@ -30,7 +30,7 @@ {% endif %} {% if request.user_profile.custom_css %} - + {% endif %} {% if not request.global_settings.enable_link_prefetch %} diff --git a/bookmarks/tests/helpers.py b/bookmarks/tests/helpers.py index ca3ea64a..bc6539ee 100644 --- a/bookmarks/tests/helpers.py +++ b/bookmarks/tests/helpers.py @@ -45,6 +45,7 @@ def setup_bookmark( favicon_file: str = "", preview_image_file: str = "", added: datetime = None, + modified: datetime = None, ): if title is None: title = get_random_string(length=32) @@ -57,13 +58,15 @@ def setup_bookmark( url = "https://example.com/" + unique_id if added is None: added = timezone.now() + if modified is None: + modified = timezone.now() bookmark = Bookmark( url=url, title=title, description=description, notes=notes, date_added=added, - date_modified=timezone.now(), + date_modified=modified, owner=user, is_archived=is_archived, unread=unread, @@ -320,6 +323,7 @@ def __init__( title: str = "", description: str = "", add_date: str = "", + last_modified: str = "", tags: str = "", to_read: bool = False, private: bool = True, @@ -328,6 +332,7 @@ def __init__( self.title = title self.description = description self.add_date = add_date + self.last_modified = last_modified self.tags = tags self.to_read = to_read self.private = private @@ -339,6 +344,7 @@ def render_tag(self, tag: BookmarkHtmlTag):
diff --git a/bookmarks/tests/test_auto_tagging.py b/bookmarks/tests/test_auto_tagging.py index 320003f8..b4213a1c 100644 --- a/bookmarks/tests/test_auto_tagging.py +++ b/bookmarks/tests/test_auto_tagging.py @@ -14,6 +14,20 @@ def test_auto_tag_by_domain(self): self.assertEqual(tags, {"example"}) + def test_auto_tag_by_domain_handles_invalid_urls(self): + script = """ + example.com example + test.com test + """ + + url = "https://" + tags = auto_tagging.get_tags(script, url) + self.assertEqual(tags, set([])) + + url = "example.com" + tags = auto_tagging.get_tags(script, url) + self.assertEqual(tags, set([])) + def test_auto_tag_by_domain_works_with_port(self): script = """ example.com example diff --git a/bookmarks/tests/test_custom_css.py b/bookmarks/tests/test_custom_css.py deleted file mode 100644 index ce0e91ea..00000000 --- a/bookmarks/tests/test_custom_css.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.test import TestCase -from django.urls import reverse - -from bookmarks.tests.helpers import BookmarkFactoryMixin - - -class CustomCssTestCase(TestCase, BookmarkFactoryMixin): - def setUp(self): - self.client.force_login(self.get_or_create_test_user()) - - def test_does_not_render_custom_style_tag_by_default(self): - response = self.client.get(reverse("bookmarks:index")) - self.assertNotContains(response, "") diff --git a/bookmarks/tests/test_custom_css_view.py b/bookmarks/tests/test_custom_css_view.py new file mode 100644 index 00000000..e5d4c070 --- /dev/null +++ b/bookmarks/tests/test_custom_css_view.py @@ -0,0 +1,28 @@ +from django.test import TestCase +from django.urls import reverse + +from bookmarks.tests.helpers import BookmarkFactoryMixin + + +class CustomCssViewTestCase(TestCase, BookmarkFactoryMixin): + def setUp(self) -> None: + user = self.get_or_create_test_user() + self.client.force_login(user) + + def test_with_empty_css(self): + response = self.client.get(reverse("bookmarks:custom_css")) + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "text/css") + self.assertEqual(response.headers["Cache-Control"], "public, max-age=2592000") + self.assertEqual(response.content.decode(), "") + + def test_with_custom_css(self): + css = "body { background-color: red; }" + self.user.profile.custom_css = css + self.user.profile.save() + + response = self.client.get(reverse("bookmarks:custom_css")) + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "text/css") + self.assertEqual(response.headers["Cache-Control"], "public, max-age=2592000") + self.assertEqual(response.content.decode(), css) diff --git a/bookmarks/tests/test_exporter.py b/bookmarks/tests/test_exporter.py index 24578662..2056bc73 100644 --- a/bookmarks/tests/test_exporter.py +++ b/bookmarks/tests/test_exporter.py @@ -1,5 +1,6 @@ +from datetime import datetime, timezone + from django.test import TestCase -from django.utils import timezone from bookmarks.services import exporter from bookmarks.tests.helpers import BookmarkFactoryMixin @@ -7,20 +8,19 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin): def test_export_bookmarks(self): - added = timezone.now() - timestamp = int(added.timestamp()) - bookmarks = [ self.setup_bookmark( url="https://example.com/1", title="Title 1", - added=added, + added=datetime.fromtimestamp(1, timezone.utc), + modified=datetime.fromtimestamp(11, timezone.utc), description="Example description", ), self.setup_bookmark( url="https://example.com/2", title="Title 2", - added=added, + added=datetime.fromtimestamp(2, timezone.utc), + modified=datetime.fromtimestamp(22, timezone.utc), tags=[ self.setup_tag(name="tag1"), self.setup_tag(name="tag2"), @@ -28,15 +28,24 @@ def test_export_bookmarks(self): ], ), self.setup_bookmark( - url="https://example.com/3", title="Title 3", added=added, unread=True + url="https://example.com/3", + title="Title 3", + added=datetime.fromtimestamp(3, timezone.utc), + modified=datetime.fromtimestamp(33, timezone.utc), + unread=True, ), self.setup_bookmark( - url="https://example.com/4", title="Title 4", added=added, shared=True + url="https://example.com/4", + title="Title 4", + added=datetime.fromtimestamp(4, timezone.utc), + modified=datetime.fromtimestamp(44, timezone.utc), + shared=True, ), self.setup_bookmark( url="https://example.com/5", title="Title 5", - added=added, + added=datetime.fromtimestamp(5, timezone.utc), + modified=datetime.fromtimestamp(55, timezone.utc), shared=True, description="Example description", notes="Example notes", @@ -44,20 +53,23 @@ def test_export_bookmarks(self): self.setup_bookmark( url="https://example.com/6", title="Title 6", - added=added, + added=datetime.fromtimestamp(6, timezone.utc), + modified=datetime.fromtimestamp(66, timezone.utc), shared=True, notes="Example notes", ), self.setup_bookmark( url="https://example.com/7", title="Title 7", - added=added, + added=datetime.fromtimestamp(7, timezone.utc), + modified=datetime.fromtimestamp(77, timezone.utc), is_archived=True, ), self.setup_bookmark( url="https://example.com/8", title="Title 8", - added=added, + added=datetime.fromtimestamp(8, timezone.utc), + modified=datetime.fromtimestamp(88, timezone.utc), tags=[self.setup_tag(name="tag4"), self.setup_tag(name="tag5")], is_archived=True, ), @@ -65,17 +77,17 @@ def test_export_bookmarks(self): html = exporter.export_netscape_html(bookmarks) lines = [ - f'
Title 1', + '
Title 1', "
Example description", - f'
Title 2', - f'
Title 3', - f'
Title 4', - f'
Title 5', + '
Title 2', + '
Title 3', + '
Title 4', + '
Title 5', "
Example description[linkding-notes]Example notes[/linkding-notes]", - f'
Title 6', + '
Title 6', "
[linkding-notes]Example notes[/linkding-notes]", - f'
Title 7', - f'
Title 8', + '
Title 7', + '
Title 8', ] self.assertIn("\n\r".join(lines), html) diff --git a/bookmarks/tests/test_importer.py b/bookmarks/tests/test_importer.py index 56b97ce3..b632d621 100644 --- a/bookmarks/tests/test_importer.py +++ b/bookmarks/tests/test_importer.py @@ -26,6 +26,9 @@ def assertBookmarksImported(self, html_tags: List[BookmarkHtmlTag]): self.assertEqual(bookmark.title, html_tag.title) self.assertEqual(bookmark.description, html_tag.description) self.assertEqual(bookmark.date_added, parse_timestamp(html_tag.add_date)) + self.assertEqual( + bookmark.date_modified, parse_timestamp(html_tag.last_modified) + ) self.assertEqual(bookmark.unread, html_tag.to_read) self.assertEqual(bookmark.shared, not html_tag.private) @@ -45,6 +48,7 @@ def test_import(self): title="Example title", description="Example description", add_date="1", + last_modified="11", tags="example-tag", ), BookmarkHtmlTag( @@ -52,6 +56,7 @@ def test_import(self): title="Foo title", description="", add_date="2", + last_modified="22", tags="", ), BookmarkHtmlTag( @@ -59,6 +64,7 @@ def test_import(self): title="Bar title", description="Bar description", add_date="3", + last_modified="33", tags="bar-tag, other-tag", ), BookmarkHtmlTag( @@ -66,6 +72,7 @@ def test_import(self): title="Baz title", description="Baz description", add_date="4", + last_modified="44", to_read=True, ), ] @@ -90,6 +97,7 @@ def test_synchronize(self): title="Example title", description="Example description", add_date="1", + last_modified="11", tags="example-tag", ), BookmarkHtmlTag( @@ -97,6 +105,7 @@ def test_synchronize(self): title="Foo title", description="", add_date="2", + last_modified="22", tags="", ), BookmarkHtmlTag( @@ -104,20 +113,23 @@ def test_synchronize(self): title="Bar title", description="Bar description", add_date="3", + last_modified="33", tags="bar-tag, other-tag", ), BookmarkHtmlTag( href="https://example.com/unread", title="Unread title", description="Unread description", - add_date="3", + add_date="4", + last_modified="44", to_read=True, ), BookmarkHtmlTag( href="https://example.com/private", title="Private title", description="Private description", - add_date="4", + add_date="5", + last_modified="55", private=True, ), ] @@ -136,6 +148,7 @@ def test_synchronize(self): title="Updated Example title", description="Updated Example description", add_date="111", + last_modified="1111", tags="updated-example-tag", ), BookmarkHtmlTag( @@ -143,6 +156,7 @@ def test_synchronize(self): title="Updated Foo title", description="Updated Foo description", add_date="222", + last_modified="2222", tags="new-tag", ), BookmarkHtmlTag( @@ -150,6 +164,7 @@ def test_synchronize(self): title="Updated Bar title", description="Updated Bar description", add_date="333", + last_modified="3333", tags="updated-bar-tag, updated-other-tag", ), BookmarkHtmlTag( @@ -157,6 +172,7 @@ def test_synchronize(self): title="Unread title", description="Unread description", add_date="3", + last_modified="3", to_read=False, ), BookmarkHtmlTag( @@ -164,9 +180,15 @@ def test_synchronize(self): title="Private title", description="Private description", add_date="4", + last_modified="4", private=False, ), - BookmarkHtmlTag(href="https://baz.com", add_date="444", tags="baz-tag"), + BookmarkHtmlTag( + href="https://baz.com", + add_date="444", + last_modified="4444", + tags="baz-tag", + ), ] # Import updated data @@ -291,6 +313,19 @@ def test_use_current_date_when_no_add_date(self): Bookmark.objects.all()[0].date_added, timezone.datetime(2021, 1, 1) ) + def test_use_add_date_when_no_last_modified(self): + test_html = self.render_html( + tags_html=f""" +
Example.com +
Example.com + """ + ) + + import_netscape_html(test_html, self.get_or_create_test_user()) + + self.assertEqual(Bookmark.objects.count(), 1) + self.assertEqual(Bookmark.objects.all()[0].date_modified, parse_timestamp("1")) + def test_keep_title_if_imported_bookmark_has_empty_title(self): test_html = self.render_html( tags=[BookmarkHtmlTag(href="https://example.com", title="Example.com")] diff --git a/bookmarks/tests/test_layout.py b/bookmarks/tests/test_layout.py index e7fb2636..70122c02 100644 --- a/bookmarks/tests/test_layout.py +++ b/bookmarks/tests/test_layout.py @@ -2,10 +2,10 @@ from django.urls import reverse from bookmarks.models import GlobalSettings -from bookmarks.tests.helpers import BookmarkFactoryMixin +from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin -class LayoutTestCase(TestCase, BookmarkFactoryMixin): +class LayoutTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): def setUp(self) -> None: user = self.get_or_create_test_user() @@ -63,3 +63,38 @@ def test_metadata_should_respect_prefetch_links_setting(self): html, count=0, ) + + def test_does_not_link_custom_css_when_empty(self): + response = self.client.get(reverse("bookmarks:index")) + html = response.content.decode() + soup = self.make_soup(html) + + link = soup.select_one("link[rel='stylesheet'][href*='custom_css']") + self.assertIsNone(link) + + def test_does_link_custom_css_when_not_empty(self): + profile = self.get_or_create_test_user().profile + profile.custom_css = "body { background-color: red; }" + profile.save() + + response = self.client.get(reverse("bookmarks:index")) + html = response.content.decode() + soup = self.make_soup(html) + + link = soup.select_one("link[rel='stylesheet'][href*='custom_css']") + self.assertIsNotNone(link) + + def test_custom_css_link_href(self): + profile = self.get_or_create_test_user().profile + profile.custom_css = "body { background-color: red; }" + profile.save() + + response = self.client.get(reverse("bookmarks:index")) + html = response.content.decode() + soup = self.make_soup(html) + + link = soup.select_one("link[rel='stylesheet'][href*='custom_css']") + expected_url = ( + reverse("bookmarks:custom_css") + f"?hash={profile.custom_css_hash}" + ) + self.assertEqual(link["href"], expected_url) diff --git a/bookmarks/tests/test_parser.py b/bookmarks/tests/test_parser.py index 16072175..3e82980b 100644 --- a/bookmarks/tests/test_parser.py +++ b/bookmarks/tests/test_parser.py @@ -18,6 +18,7 @@ def assertTagsEqual( self.assertEqual(bookmark.href, html_tag.href) self.assertEqual(bookmark.title, html_tag.title) self.assertEqual(bookmark.date_added, html_tag.add_date) + self.assertEqual(bookmark.date_modified, html_tag.last_modified) self.assertEqual(bookmark.description, html_tag.description) self.assertEqual(bookmark.tag_names, parse_tag_string(html_tag.tags)) self.assertEqual(bookmark.to_read, html_tag.to_read) @@ -30,6 +31,7 @@ def test_parse_bookmarks(self): title="Example title", description="Example description", add_date="1", + last_modified="11", tags="example-tag", ), BookmarkHtmlTag( @@ -37,6 +39,7 @@ def test_parse_bookmarks(self): title="Foo title", description="", add_date="2", + last_modified="22", tags="", ), BookmarkHtmlTag( @@ -44,13 +47,14 @@ def test_parse_bookmarks(self): title="Bar title", description="Bar description", add_date="3", + last_modified="33", tags="bar-tag, other-tag", ), BookmarkHtmlTag( href="https://example.com/baz", title="Baz title", description="Baz description", - add_date="3", + add_date="4", to_read=True, ), ] @@ -72,9 +76,17 @@ def test_reset_properties_after_adding_bookmark(self): title="Example title", description="Example description", add_date="1", + last_modified="1", tags="example-tag", ), - BookmarkHtmlTag(href="", title="", description="", add_date="", tags=""), + BookmarkHtmlTag( + href="", + title="", + description="", + add_date="", + last_modified="", + tags="", + ), ] html = self.render_html(html_tags) bookmarks = parse(html) diff --git a/bookmarks/tests/test_settings_general_view.py b/bookmarks/tests/test_settings_general_view.py index 534b952c..10f5cf72 100644 --- a/bookmarks/tests/test_settings_general_view.py +++ b/bookmarks/tests/test_settings_general_view.py @@ -1,3 +1,4 @@ +import hashlib import random from unittest.mock import patch, Mock @@ -217,6 +218,31 @@ def test_update_profile_should_not_be_called_without_respective_form_action(self self.assertEqual(self.user.profile.theme, UserProfile.THEME_AUTO) self.assertSuccessMessage(html, "Profile updated", count=0) + def test_update_profile_updates_custom_css_hash(self): + form_data = self.create_profile_form_data( + { + "custom_css": "body { background-color: #000; }", + } + ) + self.client.post(reverse("bookmarks:settings.update"), form_data, follow=True) + self.user.profile.refresh_from_db() + + expected_hash = hashlib.md5(form_data["custom_css"].encode("utf-8")).hexdigest() + self.assertEqual(expected_hash, self.user.profile.custom_css_hash) + + form_data["custom_css"] = "body { background-color: #fff; }" + self.client.post(reverse("bookmarks:settings.update"), form_data, follow=True) + self.user.profile.refresh_from_db() + + expected_hash = hashlib.md5(form_data["custom_css"].encode("utf-8")).hexdigest() + self.assertEqual(expected_hash, self.user.profile.custom_css_hash) + + form_data["custom_css"] = "" + self.client.post(reverse("bookmarks:settings.update"), form_data, follow=True) + self.user.profile.refresh_from_db() + + self.assertEqual("", self.user.profile.custom_css_hash) + def test_enable_favicons_should_schedule_icon_update(self): with patch.object( tasks, "schedule_bookmarks_without_favicons" diff --git a/bookmarks/tests/test_singlefile_service.py b/bookmarks/tests/test_singlefile_service.py index b9b0f7a7..13ca3bb7 100644 --- a/bookmarks/tests/test_singlefile_service.py +++ b/bookmarks/tests/test_singlefile_service.py @@ -66,7 +66,7 @@ def test_create_snapshot_empty_options(self): '--browser-arg="--headless=new"', '--browser-arg="--user-data-dir=./chromium-profile"', '--browser-arg="--no-sandbox"', - '--browser-arg="--load-extension=uBlock0.chromium"', + '--browser-arg="--load-extension=uBOLite.chromium.mv3"', "http://example.com", self.html_filepath + ".tmp", ] @@ -88,7 +88,7 @@ def test_create_snapshot_custom_options(self): '--browser-arg="--headless=new"', '--browser-arg="--user-data-dir=./chromium-profile"', '--browser-arg="--no-sandbox"', - '--browser-arg="--load-extension=uBlock0.chromium"', + '--browser-arg="--load-extension=uBOLite.chromium.mv3"', "--some-option", "some value", "--another-option", diff --git a/bookmarks/urls.py b/bookmarks/urls.py index 97b8ab1f..0372e192 100644 --- a/bookmarks/urls.py +++ b/bookmarks/urls.py @@ -65,4 +65,6 @@ path("health", views.health, name="health"), # Manifest path("manifest.json", views.manifest, name="manifest"), + # Custom CSS + path("custom_css", views.custom_css, name="custom_css"), ] diff --git a/bookmarks/views/__init__.py b/bookmarks/views/__init__.py index b9a9f0e4..53d66ad1 100644 --- a/bookmarks/views/__init__.py +++ b/bookmarks/views/__init__.py @@ -4,4 +4,5 @@ from .toasts import * from .health import health from .manifest import manifest +from .custom_css import custom_css from .root import root diff --git a/bookmarks/views/custom_css.py b/bookmarks/views/custom_css.py new file mode 100644 index 00000000..d2b7ebc5 --- /dev/null +++ b/bookmarks/views/custom_css.py @@ -0,0 +1,10 @@ +from django.http import HttpResponse + +custom_css_cache_max_age = 2592000 # 30 days + + +def custom_css(request): + css = request.user_profile.custom_css + response = HttpResponse(css, content_type="text/css") + response["Cache-Control"] = f"public, max-age={custom_css_cache_max_age}" + return response diff --git a/docker/alpine.Dockerfile b/docker/alpine.Dockerfile index 3e2166bf..4f7bb3ea 100644 --- a/docker/alpine.Dockerfile +++ b/docker/alpine.Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine AS node-build +FROM node:20-alpine AS node-build WORKDIR /etc/linkding # install build dependencies COPY rollup.config.mjs postcss.config.js package.json package-lock.json ./ @@ -10,7 +10,7 @@ COPY bookmarks/styles ./bookmarks/styles RUN npm run build -FROM python:3.12.6-alpine3.20 AS python-base +FROM python:3.12.6-alpine3.20 AS build-deps # Add required packages # alpine-sdk linux-headers pkgconfig: build Python packages from source # libpq-dev: build Postgres client from source @@ -18,24 +18,8 @@ FROM python:3.12.6-alpine3.20 AS python-base # libffi-dev openssl-dev rust cargo: build Python cryptography from source RUN apk update && apk add alpine-sdk linux-headers libpq-dev pkgconfig icu-dev sqlite-dev libffi-dev openssl-dev rust cargo WORKDIR /etc/linkding - - -FROM python-base AS python-build -# install build dependencies +# install python dependencies COPY requirements.txt requirements.txt -RUN pip install -U pip && pip install -r requirements.txt -# copy files needed for Django build -COPY . . -COPY --from=node-build /etc/linkding . -# remove style sources -RUN rm -rf bookmarks/styles -# run Django part of the build -RUN mkdir data && \ - python manage.py collectstatic - - -FROM python-base AS prod-deps -COPY requirements.txt ./requirements.txt # Need to build psycopg2 from source for ARM platforms RUN sed -i 's/psycopg2-binary/psycopg2/g' requirements.txt RUN mkdir /opt/venv && \ @@ -44,7 +28,7 @@ RUN mkdir /opt/venv && \ /opt/venv/bin/pip install -r requirements.txt -FROM python-base AS compile-icu +FROM build-deps AS compile-icu # Defines SQLite version # Since this is only needed for downloading the header files this probably # doesn't need to be up-to-date, assuming the SQLite APIs used by the ICU @@ -74,19 +58,23 @@ RUN set -x ; \ addgroup -g 82 -S www-data ; \ adduser -u 82 -D -S -G www-data www-data && exit 0 ; exit 1 WORKDIR /etc/linkding -# copy prod dependencies -COPY --from=prod-deps /opt/venv /opt/venv -# copy output from build stage -COPY --from=python-build /etc/linkding/static static/ +# copy python dependencies +COPY --from=build-deps /opt/venv /opt/venv +# copy output from node build +COPY --from=node-build /etc/linkding/bookmarks/static bookmarks/static/ # copy compiled icu extension COPY --from=compile-icu /etc/linkding/libicu.so libicu.so # copy application code COPY . . +# Activate virtual env +ENV VIRTUAL_ENV=/opt/venv +ENV PATH=/opt/venv/bin:$PATH +# Generate static files, remove source styles that are not needed +RUN mkdir data && \ + python manage.py collectstatic + # Expose uwsgi server at port 9090 EXPOSE 9090 -# Activate virtual env -ENV VIRTUAL_ENV /opt/venv -ENV PATH /opt/venv/bin:$PATH # Allow running containers as an an arbitrary user in the root group, to support deployment scenarios like OpenShift, Podman RUN chmod g+w . && \ chmod +x ./bootstrap.sh @@ -100,18 +88,20 @@ CMD ["./bootstrap.sh"] FROM node:18-alpine AS ublock-build WORKDIR /etc/linkding # Install necessary tools -RUN apk add --no-cache curl jq unzip -# Fetch the latest release tag -# Download the library -# Unzip the library -RUN TAG=$(curl -sL https://api.github.com/repos/gorhill/uBlock/releases/latest | jq -r '.tag_name') && \ - DOWNLOAD_URL=https://github.com/gorhill/uBlock/releases/download/$TAG/uBlock0_$TAG.chromium.zip && \ - curl -L -o uBlock0.zip $DOWNLOAD_URL && \ - unzip uBlock0.zip -# Patch assets.json to enable easylist-cookies by default -RUN curl -L -o ./uBlock0.chromium/assets/thirdparties/easylist/easylist-cookies.txt https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt -RUN jq '."assets.json" |= del(.cdnURLs) | ."assets.json".contentURL = ["assets/assets.json"] | ."fanboy-cookiemonster" |= del(.off) | ."fanboy-cookiemonster".contentURL += ["assets/thirdparties/easylist/easylist-cookies.txt"]' ./uBlock0.chromium/assets/assets.json > temp.json && \ - mv temp.json ./uBlock0.chromium/assets/assets.json +# Download and unzip the latest uBlock Origin Lite release +# Patch manifest to enable annoyances by default +# Patch ruleset-manager.js to use rulesets enabled in manifest by default +RUN apk add --no-cache curl jq unzip && \ + TAG=$(curl -sL https://api.github.com/repos/uBlockOrigin/uBOL-home/releases/latest | jq -r '.tag_name') && \ + DOWNLOAD_URL=https://github.com/uBlockOrigin/uBOL-home/releases/download/$TAG/$TAG.chromium.mv3.zip && \ + echo "Downloading $DOWNLOAD_URL" && \ + curl -L -o uBOLite.zip $DOWNLOAD_URL && \ + unzip uBOLite.zip -d uBOLite.chromium.mv3 && \ + rm uBOLite.zip && \ + jq '.declarative_net_request.rule_resources |= map(if .id == "annoyances-overlays" or .id == "annoyances-cookies" or .id == "annoyances-social" or .id == "annoyances-widgets" or .id == "annoyances-others" then .enabled = true else . end)' \ + uBOLite.chromium.mv3/manifest.json > temp.json && \ + mv temp.json uBOLite.chromium.mv3/manifest.json && \ + sed -i 's/const out = \[ '\''default'\'' \];/const out = await dnr.getEnabledRulesets();/' uBOLite.chromium.mv3/js/ruleset-manager.js FROM linkding AS linkding-plus @@ -119,9 +109,11 @@ FROM linkding AS linkding-plus RUN apk update && apk add nodejs npm chromium # install single-file from fork for now, which contains several hotfixes RUN npm install -g https://github.com/sissbruecker/single-file-cli/tarball/4c54b3bc704cfb3e96cec2d24854caca3df0b3b6 -# copy uBlock0 -COPY --from=ublock-build /etc/linkding/uBlock0.chromium uBlock0.chromium/ -# create chromium profile folder for user running background tasks -RUN mkdir -p chromium-profile && chown -R www-data:www-data chromium-profile +# copy uBlock +COPY --from=ublock-build /etc/linkding/uBOLite.chromium.mv3 uBOLite.chromium.mv3/ +# create chromium profile folder for user running background tasks and set permissions +RUN mkdir -p chromium-profile && \ + chown -R www-data:www-data chromium-profile && \ + chown -R www-data:www-data uBOLite.chromium.mv3 # enable snapshot support ENV LD_ENABLE_SNAPSHOTS=True diff --git a/docker/default.Dockerfile b/docker/default.Dockerfile index 113d6166..242567c5 100644 --- a/docker/default.Dockerfile +++ b/docker/default.Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine AS node-build +FROM node:20-alpine AS node-build WORKDIR /etc/linkding # install build dependencies COPY rollup.config.mjs postcss.config.js package.json package-lock.json ./ @@ -10,7 +10,7 @@ COPY bookmarks/styles ./bookmarks/styles RUN npm run build -FROM python:3.12.6-slim-bookworm AS python-base +FROM python:3.12.6-slim-bookworm AS build-deps # Add required packages # build-essential pkg-config: build Python packages from source # libpq-dev: build Postgres client from source @@ -20,24 +20,8 @@ RUN apt-get update && apt-get -y install build-essential pkg-config libpq-dev li RUN curl https://sh.rustup.rs -sSf | sh -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" WORKDIR /etc/linkding - - -FROM python-base AS python-build -# install build dependencies +# install python dependencies COPY requirements.txt requirements.txt -RUN pip install -U pip && pip install -r requirements.txt -# copy files needed for Django build -COPY . . -COPY --from=node-build /etc/linkding . -# remove style sources -RUN rm -rf bookmarks/styles -# run Django part of the build -RUN mkdir data && \ - python manage.py collectstatic - - -FROM python-base AS prod-deps -COPY requirements.txt ./requirements.txt # Need to build psycopg2 from source for ARM platforms RUN sed -i 's/psycopg2-binary/psycopg2/g' requirements.txt RUN mkdir /opt/venv && \ @@ -46,7 +30,7 @@ RUN mkdir /opt/venv && \ /opt/venv/bin/pip install -r requirements.txt -FROM python-base AS compile-icu +FROM build-deps AS compile-icu # Defines SQLite version # Since this is only needed for downloading the header files this probably # doesn't need to be up-to-date, assuming the SQLite APIs used by the ICU @@ -67,27 +51,31 @@ RUN wget https://www.sqlite.org/${SQLITE_RELEASE_YEAR}/sqlite-amalgamation-${SQL gcc -fPIC -shared icu.c `pkg-config --libs --cflags icu-uc icu-io` -o libicu.so -FROM python:3.12.6-slim-bookworm as linkding +FROM python:3.12.6-slim-bookworm AS linkding LABEL org.opencontainers.image.source="https://github.com/sissbruecker/linkding" +# install runtime dependencies RUN apt-get update && apt-get -y install mime-support libpq-dev libicu-dev libssl3 curl WORKDIR /etc/linkding -# copy prod dependencies -COPY --from=prod-deps /opt/venv /opt/venv -# copy output from build stage -COPY --from=python-build /etc/linkding/static static/ +# copy python dependencies +COPY --from=build-deps /opt/venv /opt/venv +# copy output from node build +COPY --from=node-build /etc/linkding/bookmarks/static bookmarks/static/ # copy compiled icu extension COPY --from=compile-icu /etc/linkding/libicu.so libicu.so # copy application code COPY . . +# Activate virtual env +ENV VIRTUAL_ENV=/opt/venv +ENV PATH=/opt/venv/bin:$PATH +# Generate static files +RUN mkdir data && \ + python manage.py collectstatic + # Expose uwsgi server at port 9090 EXPOSE 9090 -# Activate virtual env -ENV VIRTUAL_ENV /opt/venv -ENV PATH /opt/venv/bin:$PATH # Allow running containers as an an arbitrary user in the root group, to support deployment scenarios like OpenShift, Podman -RUN ["chmod", "g+w", "."] -# Run bootstrap logic -RUN ["chmod", "+x", "./bootstrap.sh"] +RUN chmod g+w . && \ + chmod +x ./bootstrap.sh HEALTHCHECK --interval=30s --retries=3 --timeout=1s \ CMD curl -f http://localhost:${LD_SERVER_PORT:-9090}/${LD_CONTEXT_PATH}health || exit 1 @@ -98,18 +86,20 @@ CMD ["./bootstrap.sh"] FROM node:18-alpine AS ublock-build WORKDIR /etc/linkding # Install necessary tools -RUN apk add --no-cache curl jq unzip -# Fetch the latest release tag -# Download the library -# Unzip the library -RUN TAG=$(curl -sL https://api.github.com/repos/gorhill/uBlock/releases/latest | jq -r '.tag_name') && \ - DOWNLOAD_URL=https://github.com/gorhill/uBlock/releases/download/$TAG/uBlock0_$TAG.chromium.zip && \ - curl -L -o uBlock0.zip $DOWNLOAD_URL && \ - unzip uBlock0.zip -# Patch assets.json to enable easylist-cookies by default -RUN curl -L -o ./uBlock0.chromium/assets/thirdparties/easylist/easylist-cookies.txt https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt -RUN jq '."assets.json" |= del(.cdnURLs) | ."assets.json".contentURL = ["assets/assets.json"] | ."fanboy-cookiemonster" |= del(.off) | ."fanboy-cookiemonster".contentURL += ["assets/thirdparties/easylist/easylist-cookies.txt"]' ./uBlock0.chromium/assets/assets.json > temp.json && \ - mv temp.json ./uBlock0.chromium/assets/assets.json +# Download and unzip the latest uBlock Origin Lite release +# Patch manifest to enable annoyances by default +# Patch ruleset-manager.js to use rulesets enabled in manifest by default +RUN apk add --no-cache curl jq unzip && \ + TAG=$(curl -sL https://api.github.com/repos/uBlockOrigin/uBOL-home/releases/latest | jq -r '.tag_name') && \ + DOWNLOAD_URL=https://github.com/uBlockOrigin/uBOL-home/releases/download/$TAG/$TAG.chromium.mv3.zip && \ + echo "Downloading $DOWNLOAD_URL" && \ + curl -L -o uBOLite.zip $DOWNLOAD_URL && \ + unzip uBOLite.zip -d uBOLite.chromium.mv3 && \ + rm uBOLite.zip && \ + jq '.declarative_net_request.rule_resources |= map(if .id == "annoyances-overlays" or .id == "annoyances-cookies" or .id == "annoyances-social" or .id == "annoyances-widgets" or .id == "annoyances-others" then .enabled = true else . end)' \ + uBOLite.chromium.mv3/manifest.json > temp.json && \ + mv temp.json uBOLite.chromium.mv3/manifest.json && \ + sed -i 's/const out = \[ '\''default'\'' \];/const out = await dnr.getEnabledRulesets();/' uBOLite.chromium.mv3/js/ruleset-manager.js FROM linkding AS linkding-plus @@ -123,9 +113,11 @@ RUN apt-get install -y gnupg2 apt-transport-https ca-certificates && \ apt-get update && apt-get install -y nodejs # install single-file from fork for now, which contains several hotfixes RUN npm install -g https://github.com/sissbruecker/single-file-cli/tarball/4c54b3bc704cfb3e96cec2d24854caca3df0b3b6 -# create chromium profile folder for user running background tasks -RUN mkdir -p chromium-profile && chown -R www-data:www-data chromium-profile -# copy uBlock0 -COPY --from=ublock-build /etc/linkding/uBlock0.chromium uBlock0.chromium/ +# copy uBlock +COPY --from=ublock-build /etc/linkding/uBOLite.chromium.mv3 uBOLite.chromium.mv3/ +# create chromium profile folder for user running background tasks and set permissions +RUN mkdir -p chromium-profile && \ + chown -R www-data:www-data chromium-profile && \ + chown -R www-data:www-data uBOLite.chromium.mv3 # enable snapshot support ENV LD_ENABLE_SNAPSHOTS=True diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index e685a807..0c271543 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -45,6 +45,9 @@ export default defineConfig({ customCss: [ './src/styles/custom.css', ], + editLink: { + baseUrl: 'https://github.com/sissbruecker/linkding/edit/master/docs/', + }, }), ], }); diff --git a/docs/package-lock.json b/docs/package-lock.json index c8c2849d..8fa89a8d 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1517,9 +1517,9 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", - "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -1529,9 +1529,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", - "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -1541,9 +1541,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", - "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -1553,9 +1553,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", - "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -1565,9 +1565,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", - "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -1577,9 +1577,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", - "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -1589,9 +1589,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", - "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -1601,9 +1601,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", - "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -1613,9 +1613,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", - "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -1625,9 +1625,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", - "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -1637,9 +1637,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", - "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -1649,9 +1649,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", - "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -1661,9 +1661,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", - "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -1673,9 +1673,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", - "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -1685,9 +1685,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", - "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -1697,9 +1697,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", - "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -6257,9 +6257,9 @@ } }, "node_modules/rollup": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", - "integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dependencies": { "@types/estree": "1.0.5" }, @@ -6271,22 +6271,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.3", - "@rollup/rollup-android-arm64": "4.21.3", - "@rollup/rollup-darwin-arm64": "4.21.3", - "@rollup/rollup-darwin-x64": "4.21.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.3", - "@rollup/rollup-linux-arm-musleabihf": "4.21.3", - "@rollup/rollup-linux-arm64-gnu": "4.21.3", - "@rollup/rollup-linux-arm64-musl": "4.21.3", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", - "@rollup/rollup-linux-riscv64-gnu": "4.21.3", - "@rollup/rollup-linux-s390x-gnu": "4.21.3", - "@rollup/rollup-linux-x64-gnu": "4.21.3", - "@rollup/rollup-linux-x64-musl": "4.21.3", - "@rollup/rollup-win32-arm64-msvc": "4.21.3", - "@rollup/rollup-win32-ia32-msvc": "4.21.3", - "@rollup/rollup-win32-x64-msvc": "4.21.3", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, diff --git a/package-lock.json b/package-lock.json index 4de7dc96..49976af9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -233,9 +233,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz", - "integrity": "sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -246,9 +246,9 @@ "peer": true }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.4.tgz", - "integrity": "sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -259,9 +259,9 @@ "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.4.tgz", - "integrity": "sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -272,9 +272,9 @@ "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.4.tgz", - "integrity": "sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -285,9 +285,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.4.tgz", - "integrity": "sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -298,9 +298,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.4.tgz", - "integrity": "sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -311,9 +311,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.4.tgz", - "integrity": "sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -324,9 +324,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.4.tgz", - "integrity": "sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -337,9 +337,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.4.tgz", - "integrity": "sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -350,9 +350,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.4.tgz", - "integrity": "sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -363,9 +363,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.4.tgz", - "integrity": "sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -376,9 +376,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.4.tgz", - "integrity": "sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -389,9 +389,9 @@ "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.4.tgz", - "integrity": "sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -402,9 +402,9 @@ "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.4.tgz", - "integrity": "sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -415,9 +415,9 @@ "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.4.tgz", - "integrity": "sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -428,9 +428,9 @@ "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.4.tgz", - "integrity": "sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -2054,9 +2054,9 @@ } }, "node_modules/rollup": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.16.4.tgz", - "integrity": "sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "peer": true, "dependencies": { "@types/estree": "1.0.5" @@ -2069,22 +2069,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.16.4", - "@rollup/rollup-android-arm64": "4.16.4", - "@rollup/rollup-darwin-arm64": "4.16.4", - "@rollup/rollup-darwin-x64": "4.16.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.16.4", - "@rollup/rollup-linux-arm-musleabihf": "4.16.4", - "@rollup/rollup-linux-arm64-gnu": "4.16.4", - "@rollup/rollup-linux-arm64-musl": "4.16.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.16.4", - "@rollup/rollup-linux-riscv64-gnu": "4.16.4", - "@rollup/rollup-linux-s390x-gnu": "4.16.4", - "@rollup/rollup-linux-x64-gnu": "4.16.4", - "@rollup/rollup-linux-x64-musl": "4.16.4", - "@rollup/rollup-win32-arm64-msvc": "4.16.4", - "@rollup/rollup-win32-ia32-msvc": "4.16.4", - "@rollup/rollup-win32-x64-msvc": "4.16.4", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, diff --git a/package.json b/package.json index e0f1eb4d..7e9cb445 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "linkding", - "version": "1.35.0", + "version": "1.36.0", "description": "", "main": "index.js", "scripts": { diff --git a/scripts/setup-ublock.sh b/scripts/setup-ublock.sh index 415f0d65..568ea8a4 100755 --- a/scripts/setup-ublock.sh +++ b/scripts/setup-ublock.sh @@ -1,13 +1,18 @@ -rm -rf ublock0.chromium +rm -rf uBOLite.chromium.mv3 -TAG=$(curl -sL https://api.github.com/repos/gorhill/uBlock/releases/latest | jq -r '.tag_name') -DOWNLOAD_URL=https://github.com/gorhill/uBlock/releases/download/$TAG/uBlock0_$TAG.chromium.zip -curl -L -o uBlock0.zip $DOWNLOAD_URL -unzip uBlock0.zip -rm uBlock0.zip +# Download uBlock Origin Lite +TAG=$(curl -sL https://api.github.com/repos/uBlockOrigin/uBOL-home/releases/latest | jq -r '.tag_name') +DOWNLOAD_URL=https://github.com/uBlockOrigin/uBOL-home/releases/download/$TAG/$TAG.chromium.mv3.zip +echo "Downloading $DOWNLOAD_URL" +curl -L -o uBOLite.zip $DOWNLOAD_URL +unzip uBOLite.zip -d uBOLite.chromium.mv3 +rm uBOLite.zip -curl -L -o ./uBlock0.chromium/assets/thirdparties/easylist/easylist-cookies.txt https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt -jq '."assets.json" |= del(.cdnURLs) | ."assets.json".contentURL = ["assets/assets.json"] | ."fanboy-cookiemonster" |= del(.off) | ."fanboy-cookiemonster".contentURL += ["assets/thirdparties/easylist/easylist-cookies.txt"]' ./uBlock0.chromium/assets/assets.json > temp.json -mv temp.json ./uBlock0.chromium/assets/assets.json +# Patch uBlock Origin Lite to respect rulesets enabled in manifest.json +sed -i '' "s/const out = \[ 'default' \];/const out = await dnr.getEnabledRulesets();/" uBOLite.chromium.mv3/js/ruleset-manager.js + +# Enable annoyances rulesets in manifest.json +jq '.declarative_net_request.rule_resources |= map(if .id == "annoyances-overlays" or .id == "annoyances-cookies" or .id == "annoyances-social" or .id == "annoyances-widgets" or .id == "annoyances-others" then .enabled = true else . end)' uBOLite.chromium.mv3/manifest.json > temp.json +mv temp.json uBOLite.chromium.mv3/manifest.json mkdir -p chromium-profile diff --git a/siteroot/settings/base.py b/siteroot/settings/base.py index 165a32df..be9860b3 100644 --- a/siteroot/settings/base.py +++ b/siteroot/settings/base.py @@ -143,14 +143,6 @@ # Collect static files in static folder STATIC_ROOT = os.path.join(BASE_DIR, "static") -STATICFILES_DIRS = [ - # Resolve theme files from style source folder - os.path.join(BASE_DIR, "bookmarks", "styles"), - # Resolve downloaded files in dev environment - os.path.join(BASE_DIR, "data", "favicons"), - os.path.join(BASE_DIR, "data", "previews"), -] - # REST framework REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ @@ -300,7 +292,7 @@ '--browser-arg="--headless=new"', '--browser-arg="--user-data-dir=./chromium-profile"', '--browser-arg="--no-sandbox"', - '--browser-arg="--load-extension=uBlock0.chromium"', + '--browser-arg="--load-extension=uBOLite.chromium.mv3"', ] ), ) diff --git a/siteroot/settings/dev.py b/siteroot/settings/dev.py index e40315b3..a4ed087c 100644 --- a/siteroot/settings/dev.py +++ b/siteroot/settings/dev.py @@ -20,6 +20,14 @@ # Allow access through ngrok CSRF_TRUSTED_ORIGINS = ["https://*.ngrok-free.app"] +STATICFILES_DIRS = [ + # Resolve theme files from style source folder + os.path.join(BASE_DIR, "bookmarks", "styles"), + # Resolve downloaded files in dev environment + os.path.join(BASE_DIR, "data", "favicons"), + os.path.join(BASE_DIR, "data", "previews"), +] + # Enable debug logging LOGGING = { "version": 1, diff --git a/version.txt b/version.txt index 2aeaa11e..39fc130e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.35.0 +1.36.0