Files

108 lines
2.8 KiB
Elixir
Raw Permalink Normal View History

defmodule BerrypodWeb.Helpers.SeoHelpers do
@moduledoc """
SEO-related helpers for generating structured data.
"""
import Phoenix.Component, only: [assign: 3]
alias Berrypod.Media
@doc """
Generates FAQPage JSON-LD schema from page blocks.
Extracts FAQ items from any FAQ blocks on the page and builds a valid
FAQPage schema. Returns nil if no FAQ blocks with valid content are found.
## Examples
iex> faq_json_ld([%{"type" => "faq", "settings" => %{"items" => [...]}}])
~s({"@context":"https://schema.org","@type":"FAQPage",...})
"""
def faq_json_ld(blocks) when is_list(blocks) do
questions =
blocks
|> Enum.filter(&(&1["type"] == "faq"))
|> Enum.flat_map(fn block ->
(block["settings"] || %{})["items"] || []
end)
|> Enum.filter(fn item ->
question = String.trim(item["question"] || "")
answer = String.trim(item["answer"] || "")
question != "" and answer != ""
end)
|> Enum.map(fn item ->
%{
"@type" => "Question",
"name" => item["question"],
"acceptedAnswer" => %{
"@type" => "Answer",
"text" => item["answer"]
}
}
end)
if questions != [] do
%{
"@context" => "https://schema.org",
"@type" => "FAQPage",
"mainEntity" => questions
}
|> Jason.encode!(escape: :html_safe)
else
nil
end
end
def faq_json_ld(_), do: nil
@doc """
Assigns an og_image URL to the socket based on page-specific or default image.
Priority: page-specific image > site-wide default > none
"""
def assign_og_image(socket, page, base) do
og_image_id = get_og_image_id(page)
og_image =
cond do
og_image_id ->
Media.get_image(og_image_id)
true ->
Media.get_default_og_image()
end
if og_image do
url = og_image_url(og_image, base)
assign(socket, :og_image, url)
else
socket
end
end
defp get_og_image_id(nil), do: nil
defp get_og_image_id(%{og_image_id: id}), do: id
defp get_og_image_id(page) when is_map(page), do: page[:og_image_id]
defp get_og_image_id(_), do: nil
@doc """
Generates a full URL for an OG image, preferring 1200px width for social sharing.
Falls back to the largest available width if the image is smaller than 1200px.
"""
def og_image_url(image, base) do
path =
if image.is_svg do
"/image_cache/#{image.id}.webp"
else
widths = Berrypod.Images.Optimizer.applicable_widths(image.source_width)
# Prefer 1200px or larger, otherwise use the largest available
width = Enum.find(widths, List.last(widths), &(&1 >= 1200))
"/image_cache/#{image.id}-#{width}.webp"
end
base <> path
end
end