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