Repository: sissbruecker/linkding (python)

Issue:
[New feature] Add a link to public RSS from public bookmarks page
Hello! This is my first issue.

I recently set up a public page with shared bookmarks and invited friends to follow my public RSS, but then I realized that the public RSS URL is only available in settings. I think it should be possible for a user looking at a public bookmarks page to discover the RSS link.

How about adding a `<link rel="alternate" type="application/rss+xml"` to the public bookmarks page if one is available? A RSS icon somewhere in page body would also help with discoverability.

I'm assuming that the public bookmarks page and public bookmarks RSS are always both enabled or both disabled. Does that sound correct?

(I might take a stab at adding it next week if it sounds ok? maybe good first commit for me?)

Pull request description:
Add RSS link to shared bookmarks page
Closes #879 

Patch:
diff --git a/bookmarks/templates/bookmarks/head.html b/bookmarks/templates/bookmarks/head.html
index b28a1ca..c83d16e 100644
--- a/bookmarks/templates/bookmarks/head.html
+++ b/bookmarks/templates/bookmarks/head.html
@@ -36,5 +36,8 @@
   {% if not request.global_settings.enable_link_prefetch %}
     <meta name="turbo-prefetch" content="false">
   {% endif %}
+  {% if rss_feed_url %}
+    <link rel="alternate" type="application/rss+xml" href="{{ rss_feed_url }}" />
+  {% endif %}
   <script src="{% static "bundle.js" %}?v={{ app_version }}"></script>
 </head>
diff --git a/bookmarks/views/bookmarks.py b/bookmarks/views/bookmarks.py
index 5c110bd..585e439 100644
--- a/bookmarks/views/bookmarks.py
+++ b/bookmarks/views/bookmarks.py
@@ -104,6 +104,7 @@ def shared(request):
             "tag_cloud": tag_cloud,
             "details": bookmark_details,
             "users": users,
+            "rss_feed_url": reverse("bookmarks:feeds.public_shared"),
         },
     )
 


Test patch:
diff --git a/bookmarks/tests/test_bookmark_archived_view.py b/bookmarks/tests/test_bookmark_archived_view.py
index 040a5f8..e6d92fb 100644
--- a/bookmarks/tests/test_bookmark_archived_view.py
+++ b/bookmarks/tests/test_bookmark_archived_view.py
@@ -503,3 +503,10 @@ class BookmarkArchivedViewTestCase(
         self.assertIsNotNone(soup.select_one("turbo-frame#details-modal"))
         self.assertIsNone(soup.select_one("#bookmark-list-container"))
         self.assertIsNone(soup.select_one("#tag-cloud-container"))
+
+    def test_does_not_include_rss_feed(self):
+        response = self.client.get(reverse("bookmarks:archived"))
+        soup = self.make_soup(response.content.decode())
+
+        feed = soup.select_one('head link[type="application/rss+xml"]')
+        self.assertIsNone(feed)
diff --git a/bookmarks/tests/test_bookmark_index_view.py b/bookmarks/tests/test_bookmark_index_view.py
index 8a0c680..ff7ed70 100644
--- a/bookmarks/tests/test_bookmark_index_view.py
+++ b/bookmarks/tests/test_bookmark_index_view.py
@@ -481,3 +481,10 @@ class BookmarkIndexViewTestCase(
         self.assertIsNotNone(soup.select_one("turbo-frame#details-modal"))
         self.assertIsNone(soup.select_one("#bookmark-list-container"))
         self.assertIsNone(soup.select_one("#tag-cloud-container"))
+
+    def test_does_not_include_rss_feed(self):
+        response = self.client.get(reverse("bookmarks:index"))
+        soup = self.make_soup(response.content.decode())
+
+        feed = soup.select_one('head link[type="application/rss+xml"]')
+        self.assertIsNone(feed)
diff --git a/bookmarks/tests/test_bookmark_shared_view.py b/bookmarks/tests/test_bookmark_shared_view.py
index c54886b..85a83a0 100644
--- a/bookmarks/tests/test_bookmark_shared_view.py
+++ b/bookmarks/tests/test_bookmark_shared_view.py
@@ -593,3 +593,11 @@ class BookmarkSharedViewTestCase(
         self.assertIsNotNone(soup.select_one("turbo-frame#details-modal"))
         self.assertIsNone(soup.select_one("#bookmark-list-container"))
         self.assertIsNone(soup.select_one("#tag-cloud-container"))
+
+    def test_includes_public_shared_rss_feed(self):
+        response = self.client.get(reverse("bookmarks:shared"))
+        soup = self.make_soup(response.content.decode())
+
+        feed = soup.select_one('head link[type="application/rss+xml"]')
+        self.assertIsNotNone(feed)
+        self.assertEqual(feed.attrs["href"], reverse("bookmarks:feeds.public_shared"))