add page builder polish: utility blocks, templates, duplicate
All checks were successful
deploy / deploy (push) Successful in 1m24s
All checks were successful
deploy / deploy (push) Successful in 1m24s
New block types: spacer, divider, button/CTA, video embed (YouTube, Vimeo with privacy-enhanced embeds, fallback for unknown URLs). Page templates (blank, content, landing) shown when creating custom pages. Duplicate page action on admin index with slug deduplication. Fix block picker on shop edit sidebar being cut off on mobile by accounting for bottom nav and making the grid scrollable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -965,6 +965,87 @@ defmodule BerrypodWeb.PageRenderer do
|
||||
"""
|
||||
end
|
||||
|
||||
# ── Utility blocks ──────────────────────────────────────────────
|
||||
|
||||
defp render_block(%{block: %{"type" => "spacer"}} = assigns) do
|
||||
size = get_in(assigns.block, ["settings", "size"]) || "medium"
|
||||
assigns = assign(assigns, :size, size)
|
||||
|
||||
~H"""
|
||||
<div class="block-spacer" data-size={@size} aria-hidden="true"></div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_block(%{block: %{"type" => "divider"}} = assigns) do
|
||||
style = get_in(assigns.block, ["settings", "style"]) || "line"
|
||||
assigns = assign(assigns, :style, style)
|
||||
|
||||
~H"""
|
||||
<hr class="block-divider" data-style={@style} />
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_block(%{block: %{"type" => "button"}} = assigns) do
|
||||
settings = assigns.block["settings"] || %{}
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:text, settings["text"] || "Learn more")
|
||||
|> assign(:href, settings["href"] || "/")
|
||||
|> assign(:btn_style, settings["style"] || "primary")
|
||||
|> assign(:alignment, settings["alignment"] || "centre")
|
||||
|
||||
~H"""
|
||||
<div class="block-button" data-align={@alignment}>
|
||||
<.link
|
||||
navigate={@href}
|
||||
class={if @btn_style == "outline", do: "themed-button-outline", else: "themed-button"}
|
||||
>
|
||||
{@text}
|
||||
</.link>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_block(%{block: %{"type" => "video_embed"}} = assigns) do
|
||||
settings = assigns.block["settings"] || %{}
|
||||
url = settings["url"] || ""
|
||||
caption = settings["caption"] || ""
|
||||
aspect_ratio = settings["aspect_ratio"] || "16:9"
|
||||
|
||||
{provider, embed_url} = parse_video_url(url)
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:embed_url, embed_url)
|
||||
|> assign(:provider, provider)
|
||||
|> assign(:caption, caption)
|
||||
|> assign(:aspect_ratio, aspect_ratio)
|
||||
|> assign(:raw_url, url)
|
||||
|
||||
~H"""
|
||||
<div class="page-container">
|
||||
<div :if={@provider != :unknown} class="video-embed" data-ratio={@aspect_ratio}>
|
||||
<iframe
|
||||
src={@embed_url}
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
loading="lazy"
|
||||
title={if @caption != "", do: @caption, else: "Embedded video"}
|
||||
>
|
||||
</iframe>
|
||||
</div>
|
||||
<p :if={@provider == :unknown && @raw_url != ""} class="video-embed-fallback">
|
||||
<a href={@raw_url} target="_blank" rel="noopener noreferrer">
|
||||
{if @caption != "", do: @caption, else: "Watch video"}
|
||||
</a>
|
||||
</p>
|
||||
<p :if={@caption != "" && @provider != :unknown} class="video-embed-caption">{@caption}</p>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
# ── Fallback ────────────────────────────────────────────────────
|
||||
|
||||
defp render_block(assigns) do
|
||||
@@ -1073,6 +1154,24 @@ defmodule BerrypodWeb.PageRenderer do
|
||||
end
|
||||
end
|
||||
|
||||
@youtube_re ~r/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/
|
||||
@vimeo_re ~r/vimeo\.com\/(?:video\/)?(\d+)/
|
||||
|
||||
defp parse_video_url(url) when is_binary(url) do
|
||||
cond do
|
||||
match = Regex.run(@youtube_re, url) ->
|
||||
{:youtube, "https://www.youtube-nocookie.com/embed/#{Enum.at(match, 1)}"}
|
||||
|
||||
match = Regex.run(@vimeo_re, url) ->
|
||||
{:vimeo, "https://player.vimeo.com/video/#{Enum.at(match, 1)}?dnt=1"}
|
||||
|
||||
true ->
|
||||
{:unknown, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_video_url(_), do: {:unknown, nil}
|
||||
|
||||
def format_order_status("unfulfilled"), do: "Being prepared"
|
||||
def format_order_status("submitted"), do: "Sent to printer"
|
||||
def format_order_status("processing"), do: "In production"
|
||||
|
||||
Reference in New Issue
Block a user