Add RSS subscribe links with icon across blog pages, footer, and head
- Add <.rss_icon> inline SVG component in core_components.ex - Inject RSS <link> tags into <head> on blog pages for auto-discovery - Show RSS icon next to blog title on index and tag pages - Show RSS icon on post pages next to back-link - Add Subscribe section in footer with links to both feeds - Wire feed URL through blog controller (index, show, tag actions) 174 tests pass, credo clean.
This commit is contained in:
parent
9238ecb7f3
commit
855b6cb590
@ -420,6 +420,31 @@ defmodule FirehoseWeb.CoreComponents do
|
|||||||
"""
|
"""
|
||||||
end
|
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"""
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
class={[@class]}
|
||||||
|
{@rest}
|
||||||
|
>
|
||||||
|
<path d="M3.75 3.75v3c0 7.454 6.046 13.5 13.5 13.5h3v-5.25a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75v1.5a8.25 8.25 0 0 1-8.25-8.25h1.5a.75.75 0 0 0 .75-.75v-3a.75.75 0 0 0-.75-.75h-5.25a.75.75 0 0 0-.75.75Z" />
|
||||||
|
<circle cx="5.25" cy="18.75" r="2.25" />
|
||||||
|
</svg>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
## JS Commands
|
## JS Commands
|
||||||
|
|
||||||
def show(js \\ %JS{}, selector) do
|
def show(js \\ %JS{}, selector) do
|
||||||
|
|||||||
@ -39,4 +39,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<footer class="border-t border-base-200 px-4 py-8 sm:px-6 lg:px-8">
|
||||||
|
<div class="mx-auto max-w-2xl space-y-3">
|
||||||
|
<h3 class="text-sm font-semibold tracking-wide text-base-content/60 uppercase">Subscribe</h3>
|
||||||
|
<div class="flex flex-wrap gap-4">
|
||||||
|
<a
|
||||||
|
href="/api/blog/engineering/feed.xml"
|
||||||
|
class="inline-flex items-center gap-2 text-sm text-base-content/70 hover:text-primary transition-colors"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<.rss_icon class="size-4 text-orange-500" />
|
||||||
|
Engineering Blog
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/api/blog/releases/feed.xml"
|
||||||
|
class="inline-flex items-center gap-2 text-sm text-base-content/70 hover:text-primary transition-colors"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<.rss_icon class="size-4 text-orange-500" />
|
||||||
|
Release Notes
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<.flash_group flash={@flash} />
|
<.flash_group flash={@flash} />
|
||||||
|
|||||||
@ -41,6 +41,9 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<meta name="twitter:card" content={meta.twitter_card} />
|
<meta name="twitter:card" content={meta.twitter_card} />
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= for feed <- get_content_for(assigns, :feed_links) do %>
|
||||||
|
<link rel="alternate" type="application/rss+xml" title={feed.title} href={feed.rss_url} />
|
||||||
|
<% end %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{@inner_content}
|
{@inner_content}
|
||||||
|
|||||||
@ -9,9 +9,11 @@ defmodule FirehoseWeb.BlogController do
|
|||||||
result = blog.paginate(page)
|
result = blog.paginate(page)
|
||||||
|
|
||||||
meta = Blogex.SEO.meta_tags_for_blog(blog, FirehoseWeb.Endpoint.url())
|
meta = Blogex.SEO.meta_tags_for_blog(blog, FirehoseWeb.Endpoint.url())
|
||||||
|
rss_url = rss_feed_url(blog)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> FirehoseWeb.Layouts.put_content_for(:meta_tags, meta)
|
|> FirehoseWeb.Layouts.put_content_for(:meta_tags, meta)
|
||||||
|
|> FirehoseWeb.Layouts.put_content_for(:feed_links, %{rss_url: rss_url, title: blog.title()})
|
||||||
|> render(:index,
|
|> render(:index,
|
||||||
page_title: blog.title(),
|
page_title: blog.title(),
|
||||||
blog_title: blog.title(),
|
blog_title: blog.title(),
|
||||||
@ -19,7 +21,8 @@ defmodule FirehoseWeb.BlogController do
|
|||||||
posts: result.entries,
|
posts: result.entries,
|
||||||
base_path: blog.base_path(),
|
base_path: blog.base_path(),
|
||||||
page: result.page,
|
page: result.page,
|
||||||
total_pages: result.total_pages
|
total_pages: result.total_pages,
|
||||||
|
rss_feed_url: rss_url
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -27,31 +30,40 @@ defmodule FirehoseWeb.BlogController do
|
|||||||
blog = conn.assigns.blog
|
blog = conn.assigns.blog
|
||||||
post = blog.get_post!(slug)
|
post = blog.get_post!(slug)
|
||||||
visibility = Blogex.Post.visibility(post)
|
visibility = Blogex.Post.visibility(post)
|
||||||
|
rss_url = rss_feed_url(blog)
|
||||||
|
|
||||||
meta = Blogex.SEO.meta_tags(post, FirehoseWeb.Endpoint.url(), blog)
|
meta = Blogex.SEO.meta_tags(post, FirehoseWeb.Endpoint.url(), blog)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> FirehoseWeb.Layouts.put_content_for(:meta_tags, meta)
|
|> FirehoseWeb.Layouts.put_content_for(:meta_tags, meta)
|
||||||
|
|> FirehoseWeb.Layouts.put_content_for(:feed_links, %{rss_url: rss_url, title: blog.title()})
|
||||||
|> render(:show,
|
|> render(:show,
|
||||||
page_title: post.title,
|
page_title: post.title,
|
||||||
post: post,
|
post: post,
|
||||||
meta: meta,
|
meta: meta,
|
||||||
base_path: blog.base_path(),
|
base_path: blog.base_path(),
|
||||||
visibility: visibility,
|
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
|
end
|
||||||
|
|
||||||
def tag(conn, %{"tag" => tag}) do
|
def tag(conn, %{"tag" => tag}) do
|
||||||
blog = conn.assigns.blog
|
blog = conn.assigns.blog
|
||||||
posts = blog.posts_by_tag(tag)
|
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}",
|
page_title: "#{blog.title()} — #{tag}",
|
||||||
blog_title: blog.title(),
|
blog_title: blog.title(),
|
||||||
tag: tag,
|
tag: tag,
|
||||||
posts: posts,
|
posts: posts,
|
||||||
base_path: blog.base_path()
|
base_path: blog.base_path(),
|
||||||
|
rss_feed_url: rss_url
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -77,4 +89,9 @@ defmodule FirehoseWeb.BlogController do
|
|||||||
_ -> 1
|
_ -> 1
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@ -1,6 +1,17 @@
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<header>
|
<header>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
|
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
|
||||||
|
<a
|
||||||
|
href={@rss_feed_url}
|
||||||
|
class="link link-hover text-orange-500 hover:text-orange-600 transition-colors"
|
||||||
|
title="Subscribe to {@blog_title} RSS feed"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<.rss_icon class="size-5" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<p :if={@blog_description} class="mt-2 text-base-content/70">{@blog_description}</p>
|
<p :if={@blog_description} class="mt-2 text-base-content/70">{@blog_description}</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,16 @@
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<a href={@base_path} class="text-sm text-primary hover:underline">← Back to posts</a>
|
<a href={@base_path} class="text-sm text-primary hover:underline">← Back to posts</a>
|
||||||
|
<a
|
||||||
|
href={@rss_feed_url}
|
||||||
|
class="link link-hover text-orange-500 hover:text-orange-600 transition-colors"
|
||||||
|
title="Subscribe to RSS feed"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<.rss_icon class="size-5" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%= if @authenticated and @visibility == :draft do %>
|
<%= if @authenticated and @visibility == :draft do %>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,6 +1,17 @@
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
<header>
|
<header>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
|
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
|
||||||
|
<a
|
||||||
|
href={@rss_feed_url}
|
||||||
|
class="link link-hover text-orange-500 hover:text-orange-600 transition-colors"
|
||||||
|
title="Subscribe to {@blog_title} RSS feed"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<.rss_icon class="size-5" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<p class="mt-2 text-base-content/70">Posts tagged "{@tag}"</p>
|
<p class="mt-2 text-base-content/70">Posts tagged "{@tag}"</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user