diff --git a/bookmarks/forms.py b/bookmarks/forms.py
index c571d0f..244fbbf 100644
--- a/bookmarks/forms.py
+++ b/bookmarks/forms.py
@@ -50,6 +50,7 @@ class BookmarkForm(forms.ModelForm):
"tag_string": request.GET.get("tags"),
"auto_close": "auto_close" in request.GET,
"unread": request.user_profile.default_mark_unread,
+ "shared": request.user_profile.default_mark_shared,
}
if instance is not None and request.method == "GET":
initial = {"tag_string": build_tag_string(instance.tag_names, " ")}
diff --git a/bookmarks/migrations/0048_userprofile_default_mark_shared.py b/bookmarks/migrations/0048_userprofile_default_mark_shared.py
new file mode 100644
index 0000000..0b149a0
--- /dev/null
+++ b/bookmarks/migrations/0048_userprofile_default_mark_shared.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.3 on 2025-08-22 17:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("bookmarks", "0047_populate_url_normalized_field"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="userprofile",
+ name="default_mark_shared",
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/bookmarks/models.py b/bookmarks/models.py
index 2e860d7..af6c3cd 100644
--- a/bookmarks/models.py
+++ b/bookmarks/models.py
@@ -479,6 +479,7 @@ class UserProfile(models.Model):
search_preferences = models.JSONField(default=dict, null=False)
enable_automatic_html_snapshots = models.BooleanField(default=True, null=False)
default_mark_unread = models.BooleanField(default=False, null=False)
+ default_mark_shared = models.BooleanField(default=False, null=False)
items_per_page = models.IntegerField(
null=False, default=30, validators=[MinValueValidator(10)]
)
@@ -520,6 +521,7 @@ class UserProfileForm(forms.ModelForm):
"display_remove_bookmark_action",
"permanent_notes",
"default_mark_unread",
+ "default_mark_shared",
"custom_css",
"auto_tagging_rules",
"items_per_page",
diff --git a/bookmarks/templates/settings/general.html b/bookmarks/templates/settings/general.html
index 16800bf..a1d0931 100644
--- a/bookmarks/templates/settings/general.html
+++ b/bookmarks/templates/settings/general.html
@@ -270,6 +270,17 @@ reddit.com/r/Music music reddit
This can be overridden when creating each new bookmark.
+
+
+
+ Sets the default state for the "Share" option when creating a new bookmark.
+ Setting this option will make all new bookmarks default to shared.
+ This can be overridden when creating each new bookmark.
+
+
@@ -404,21 +415,25 @@ reddit.com/r/Music music reddit
(function init() {
const enableSharing = document.getElementById("{{ form.enable_sharing.id_for_label }}");
const enablePublicSharing = document.getElementById("{{ form.enable_public_sharing.id_for_label }}");
+ const defaultMarkShared = document.getElementById("{{ form.default_mark_shared.id_for_label }}");
const bookmarkDescriptionDisplay = document.getElementById("{{ form.bookmark_description_display.id_for_label }}");
const bookmarkDescriptionMaxLines = document.getElementById("{{ form.bookmark_description_max_lines.id_for_label }}");
- // Automatically disable public bookmark sharing if bookmark sharing is disabled
- function updatePublicSharing() {
+ // Automatically disable public bookmark sharing and default shared option if bookmark sharing is disabled
+ function updateSharingOptions() {
if (enableSharing.checked) {
enablePublicSharing.disabled = false;
+ defaultMarkShared.disabled = false;
} else {
enablePublicSharing.disabled = true;
enablePublicSharing.checked = false;
+ defaultMarkShared.disabled = true;
+ defaultMarkShared.checked = false;
}
}
- updatePublicSharing();
- enableSharing.addEventListener("change", updatePublicSharing);
+ updateSharingOptions();
+ enableSharing.addEventListener("change", updateSharingOptions);
// Automatically hide the bookmark description max lines input if the description display is set to inline
function updateBookmarkDescriptionMaxLines() {
diff --git a/bookmarks/tests/test_bookmark_new_view.py b/bookmarks/tests/test_bookmark_new_view.py
index c4eaca3..a892197 100644
--- a/bookmarks/tests/test_bookmark_new_view.py
+++ b/bookmarks/tests/test_bookmark_new_view.py
@@ -281,3 +281,28 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
'',
html,
)
+
+ def test_should_not_check_shared_by_default(self):
+ self.user.profile.enable_sharing = True
+ self.user.profile.save()
+
+ response = self.client.get(reverse("linkding:bookmarks.new"))
+ html = response.content.decode()
+
+ self.assertInHTML(
+ '',
+ html,
+ )
+
+ def test_should_check_shared_when_configured_in_profile(self):
+ self.user.profile.enable_sharing = True
+ self.user.profile.default_mark_shared = True
+ self.user.profile.save()
+
+ response = self.client.get(reverse("linkding:bookmarks.new"))
+ html = response.content.decode()
+
+ self.assertInHTML(
+ '',
+ html,
+ )
diff --git a/bookmarks/tests/test_settings_general_view.py b/bookmarks/tests/test_settings_general_view.py
index ad250bb..c5036d5 100644
--- a/bookmarks/tests/test_settings_general_view.py
+++ b/bookmarks/tests/test_settings_general_view.py
@@ -115,6 +115,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"display_remove_bookmark_action": False,
"permanent_notes": True,
"default_mark_unread": True,
+ "default_mark_shared": True,
"custom_css": "body { background-color: #000; }",
"auto_tagging_rules": "example.com tag",
"items_per_page": "10",
@@ -188,6 +189,9 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertEqual(
self.user.profile.default_mark_unread, form_data["default_mark_unread"]
)
+ self.assertEqual(
+ self.user.profile.default_mark_shared, form_data["default_mark_shared"]
+ )
self.assertEqual(self.user.profile.custom_css, form_data["custom_css"])
self.assertEqual(
self.user.profile.auto_tagging_rules, form_data["auto_tagging_rules"]
diff --git a/bookmarks/tests_e2e/e2e_test_settings_general.py b/bookmarks/tests_e2e/e2e_test_settings_general.py
index bf5a5d0..fafe28b 100644
--- a/bookmarks/tests_e2e/e2e_test_settings_general.py
+++ b/bookmarks/tests_e2e/e2e_test_settings_general.py
@@ -18,28 +18,43 @@ class SettingsGeneralE2ETestCase(LinkdingE2ETestCase):
enable_public_sharing_label = page.get_by_text(
"Enable public bookmark sharing"
)
+ default_mark_shared = page.get_by_label(
+ "Create bookmarks as shared by default"
+ )
+ default_mark_shared_label = page.get_by_text(
+ "Create bookmarks as shared by default"
+ )
- # Public sharing is disabled by default
+ # Public sharing and default shared are disabled by default
expect(enable_sharing).not_to_be_checked()
expect(enable_public_sharing).not_to_be_checked()
expect(enable_public_sharing).to_be_disabled()
+ expect(default_mark_shared).not_to_be_checked()
+ expect(default_mark_shared).to_be_disabled()
# Enable sharing
enable_sharing_label.click()
expect(enable_sharing).to_be_checked()
expect(enable_public_sharing).not_to_be_checked()
expect(enable_public_sharing).to_be_enabled()
+ expect(default_mark_shared).not_to_be_checked()
+ expect(default_mark_shared).to_be_enabled()
- # Enable public sharing
+ # Enable public sharing and default shared
enable_public_sharing_label.click()
+ default_mark_shared_label.click()
expect(enable_public_sharing).to_be_checked()
expect(enable_public_sharing).to_be_enabled()
+ expect(default_mark_shared).to_be_checked()
+ expect(default_mark_shared).to_be_enabled()
# Disable sharing
enable_sharing_label.click()
expect(enable_sharing).not_to_be_checked()
expect(enable_public_sharing).not_to_be_checked()
expect(enable_public_sharing).to_be_disabled()
+ expect(default_mark_shared).not_to_be_checked()
+ expect(default_mark_shared).to_be_disabled()
def test_should_not_show_bookmark_description_max_lines_when_display_inline(self):
profile = self.get_or_create_test_user().profile