diff --git a/app/lib/firehose_web/components/core_components.ex b/app/lib/firehose_web/components/core_components.ex index dcb60ad..d0be6c1 100644 --- a/app/lib/firehose_web/components/core_components.ex +++ b/app/lib/firehose_web/components/core_components.ex @@ -420,6 +420,30 @@ defmodule FirehoseWeb.CoreComponents do """ end + @doc """ + Renders an RSS feed icon (inline SVG). + + ## Examples + + <.rss_icon class="size-5" /> + """ + attr :class, :string, default: "size-4" + attr :rest, :global + + def rss_icon(assigns) do + ~H""" + + + + """ + end + ## JS Commands def show(js \\ %JS{}, selector) do diff --git a/app/lib/firehose_web/components/layouts/app.html.heex b/app/lib/firehose_web/components/layouts/app.html.heex index 5d99688..d787052 100644 --- a/app/lib/firehose_web/components/layouts/app.html.heex +++ b/app/lib/firehose_web/components/layouts/app.html.heex @@ -39,4 +39,30 @@ + + <.flash_group flash={@flash} /> diff --git a/app/lib/firehose_web/components/layouts/root.html.heex b/app/lib/firehose_web/components/layouts/root.html.heex index 4147954..74b7879 100644 --- a/app/lib/firehose_web/components/layouts/root.html.heex +++ b/app/lib/firehose_web/components/layouts/root.html.heex @@ -41,6 +41,9 @@ <% end %> <% end %> + <%= for feed <- get_content_for(assigns, :feed_links) do %> + + <% end %> {@inner_content} diff --git a/app/lib/firehose_web/controllers/blog_controller.ex b/app/lib/firehose_web/controllers/blog_controller.ex index 43cab46..72fa372 100644 --- a/app/lib/firehose_web/controllers/blog_controller.ex +++ b/app/lib/firehose_web/controllers/blog_controller.ex @@ -9,9 +9,11 @@ defmodule FirehoseWeb.BlogController do result = blog.paginate(page) meta = Blogex.SEO.meta_tags_for_blog(blog, FirehoseWeb.Endpoint.url()) + rss_url = rss_feed_url(blog) conn |> FirehoseWeb.Layouts.put_content_for(:meta_tags, meta) + |> FirehoseWeb.Layouts.put_content_for(:feed_links, %{rss_url: rss_url, title: blog.title()}) |> render(:index, page_title: blog.title(), blog_title: blog.title(), @@ -19,7 +21,8 @@ defmodule FirehoseWeb.BlogController do posts: result.entries, base_path: blog.base_path(), page: result.page, - total_pages: result.total_pages + total_pages: result.total_pages, + rss_feed_url: rss_url ) end @@ -27,31 +30,40 @@ defmodule FirehoseWeb.BlogController do blog = conn.assigns.blog post = blog.get_post!(slug) visibility = Blogex.Post.visibility(post) + rss_url = rss_feed_url(blog) meta = Blogex.SEO.meta_tags(post, FirehoseWeb.Endpoint.url(), blog) conn |> FirehoseWeb.Layouts.put_content_for(:meta_tags, meta) + |> FirehoseWeb.Layouts.put_content_for(:feed_links, %{rss_url: rss_url, title: blog.title()}) |> render(:show, page_title: post.title, post: post, meta: meta, base_path: blog.base_path(), visibility: visibility, - authenticated: !!(conn.assigns[:current_scope] && conn.assigns.current_scope.user) + authenticated: !!(conn.assigns[:current_scope] && conn.assigns.current_scope.user), + rss_feed_url: rss_url ) end def tag(conn, %{"tag" => tag}) do blog = conn.assigns.blog posts = blog.posts_by_tag(tag) + rss_url = rss_feed_url(blog) - render(conn, :tag, + rss_link = %{rss_url: rss_url, title: blog.title()} + + conn + |> FirehoseWeb.Layouts.put_content_for(:feed_links, rss_link) + |> render(:tag, page_title: "#{blog.title()} — #{tag}", blog_title: blog.title(), tag: tag, posts: posts, - base_path: blog.base_path() + base_path: blog.base_path(), + rss_feed_url: rss_url ) end @@ -77,4 +89,9 @@ defmodule FirehoseWeb.BlogController do _ -> 1 end end + + defp rss_feed_url(blog) do + base_id = blog.base_path() |> String.trim_leading("/blog/") + FirehoseWeb.Endpoint.url() <> "/api/blog/#{base_id}/feed.xml" + end end diff --git a/app/lib/firehose_web/controllers/blog_html/index.html.heex b/app/lib/firehose_web/controllers/blog_html/index.html.heex index c96f07a..7a7c119 100644 --- a/app/lib/firehose_web/controllers/blog_html/index.html.heex +++ b/app/lib/firehose_web/controllers/blog_html/index.html.heex @@ -1,6 +1,17 @@
-

{@blog_title}

+

{@blog_description}

diff --git a/app/lib/firehose_web/controllers/blog_html/show.html.heex b/app/lib/firehose_web/controllers/blog_html/show.html.heex index 8f4313b..8cf0f19 100644 --- a/app/lib/firehose_web/controllers/blog_html/show.html.heex +++ b/app/lib/firehose_web/controllers/blog_html/show.html.heex @@ -1,5 +1,16 @@
- ← Back to posts + <%= if @authenticated and @visibility == :draft do %>
-

{@blog_title}

+

Posts tagged "{@tag}"

diff --git a/app/mix.exs b/app/mix.exs index a04e3e8..89cfadf 100644 --- a/app/mix.exs +++ b/app/mix.exs @@ -4,7 +4,7 @@ defmodule Firehose.MixProject do def project do [ app: :firehose, - version: "0.1.0", + version: "0.2.0", elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/app/priv/blog/release-notes/2026/07-08-v0-2-0.md b/app/priv/blog/release-notes/2026/07-08-v0-2-0.md new file mode 100644 index 0000000..8d87958 --- /dev/null +++ b/app/priv/blog/release-notes/2026/07-08-v0-2-0.md @@ -0,0 +1,16 @@ +%{ + title: "v0.2.0 — RSS Subscribe Links", + author: "Willem van den Ende", + tags: ~w(release), + description: "RSS feed subscribe links with classic waveguide icons across blog pages, post pages, and site footer." +} +--- + +RSS feeds have always been available at `/api/blog/engineering/feed.xml` and `/api/blog/releases/feed.xml`, but they were never advertised. This release adds subscribe links throughout the site. + +## What's new + +- RSS `` tags in `` on all blog pages for browser/feed reader auto-discovery +- Orange RSS icon next to blog title on index pages +- RSS icon on each post page +- Subscribe section in the footer with links to both feeds \ No newline at end of file diff --git a/app/test/firehose_web/live/microprints_live_test.exs b/app/test/firehose_web/live/microprints_live_test.exs index d42ec78..6c9723b 100644 --- a/app/test/firehose_web/live/microprints_live_test.exs +++ b/app/test/firehose_web/live/microprints_live_test.exs @@ -90,9 +90,9 @@ defmodule FirehoseWeb.MicroprintsLiveTest do assert html =~ ~s(phx-value-path="#{file_a}") assert html =~ "Collapse" - # Highlight a line in file B via direct event (avoids brittle DOM selector) - view - |> render_click("highlight_line", %{"line" => "1", "path" => file_b}) + # Highlight a line in file B by dispatching the highlight_line event + # (microprint renders multiple rect segments per line, element() can't pick one) + render_click(view, "highlight_line", %{"line" => "1", "path" => file_b}) # file A should be collapsed when highlighting a different file html = render(view)