Merge branch 'main' of dokku:firehose.apps.sustainabledelivery.com
This commit is contained in:
commit
4ead5702fb
@ -420,6 +420,30 @@ 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="M6.18 15.64a2.18 2.18 0 0 1 2.18 2.18C8.36 19 7.38 20 6.18 20C5 20 4 19 4 17.82a2.18 2.18 0 0 1 2.18-2.18M4 4.44A15.56 15.56 0 0 1 19.56 20h-2.83A12.73 12.73 0 0 0 4 7.27V4.44m0 5.66a9.9 9.9 0 0 1 9.9 9.9h-2.83A7.07 7.07 0 0 0 4 12.93v-2.83Z" />
|
||||||
|
</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>
|
||||||
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
|
<div class="flex items-center gap-3">
|
||||||
|
<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">
|
||||||
<a href={@base_path} class="text-sm text-primary hover:underline">← Back to posts</a>
|
<div class="flex items-center justify-between">
|
||||||
|
<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>
|
||||||
<h1 class="text-3xl font-bold font-display">{@blog_title}</h1>
|
<div class="flex items-center gap-3">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ defmodule Firehose.MixProject do
|
|||||||
def project do
|
def project do
|
||||||
[
|
[
|
||||||
app: :firehose,
|
app: :firehose,
|
||||||
version: "0.1.0",
|
version: "0.2.0",
|
||||||
elixir: "~> 1.15",
|
elixir: "~> 1.15",
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
|
|||||||
16
app/priv/blog/release-notes/2026/07-08-v0-2-0.md
Normal file
16
app/priv/blog/release-notes/2026/07-08-v0-2-0.md
Normal file
@ -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 `<link>` tags in `<head>` 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
|
||||||
@ -90,9 +90,9 @@ defmodule FirehoseWeb.MicroprintsLiveTest do
|
|||||||
assert html =~ ~s(phx-value-path="#{file_a}")
|
assert html =~ ~s(phx-value-path="#{file_a}")
|
||||||
assert html =~ "Collapse"
|
assert html =~ "Collapse"
|
||||||
|
|
||||||
# Highlight a line in file B via direct event (avoids brittle DOM selector)
|
# Highlight a line in file B by dispatching the highlight_line event
|
||||||
view
|
# (microprint renders multiple rect segments per line, element() can't pick one)
|
||||||
|> render_click("highlight_line", %{"line" => "1", "path" => file_b})
|
render_click(view, "highlight_line", %{"line" => "1", "path" => file_b})
|
||||||
|
|
||||||
# file A should be collapsed when highlighting a different file
|
# file A should be collapsed when highlighting a different file
|
||||||
html = render(view)
|
html = render(view)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user