add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset - Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup - Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing - Block editor: :image field type for image_text and content_body blocks - Page renderer: image_id resolution with legacy URL fallback - Mobile: bottom sheet detail panel with slide-up animation - CSS: uses correct --t-* admin theme tokens, admin-badge colour variants Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
87
lib/mix/tasks/berrypod/backfill_alt_text.ex
Normal file
87
lib/mix/tasks/berrypod/backfill_alt_text.ex
Normal file
@@ -0,0 +1,87 @@
|
||||
defmodule Mix.Tasks.Berrypod.BackfillAltText do
|
||||
@shortdoc "Backfill alt text on existing images"
|
||||
@moduledoc """
|
||||
One-time task to populate alt text on images that were uploaded before
|
||||
the alt field existed.
|
||||
|
||||
- Product images: copies alt from `product_images.alt`, falls back to product title
|
||||
- Logo: uses site name from theme settings
|
||||
- Header: "Header background"
|
||||
- Icon: "Site icon"
|
||||
- Skips images that already have alt text set
|
||||
|
||||
## Usage
|
||||
|
||||
mix berrypod.backfill_alt_text
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
alias Berrypod.Repo
|
||||
alias Berrypod.Media.Image
|
||||
alias Berrypod.Products.ProductImage
|
||||
alias Berrypod.Products.Product
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
@impl true
|
||||
def run(_args) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
backfill_product_images()
|
||||
backfill_theme_images()
|
||||
|
||||
Mix.shell().info("Alt text backfill complete.")
|
||||
end
|
||||
|
||||
defp backfill_product_images do
|
||||
# Find product images with linked image records missing alt text
|
||||
results =
|
||||
from(pi in ProductImage,
|
||||
join: i in Image,
|
||||
on: i.id == pi.image_id,
|
||||
join: p in Product,
|
||||
on: p.id == pi.product_id,
|
||||
where: is_nil(i.alt) and not is_nil(pi.image_id),
|
||||
select: {i.id, pi.alt, p.title}
|
||||
)
|
||||
|> Repo.all()
|
||||
|
||||
count =
|
||||
Enum.reduce(results, 0, fn {image_id, pi_alt, product_title}, acc ->
|
||||
alt = pi_alt || product_title || "Product image"
|
||||
|
||||
from(i in Image, where: i.id == ^image_id)
|
||||
|> Repo.update_all(set: [alt: alt])
|
||||
|
||||
acc + 1
|
||||
end)
|
||||
|
||||
Mix.shell().info(" Updated #{count} product image(s)")
|
||||
end
|
||||
|
||||
defp backfill_theme_images do
|
||||
theme = Berrypod.Settings.get_theme_settings()
|
||||
|
||||
mapping = [
|
||||
{theme.logo_image_id, theme.site_name || "Shop logo"},
|
||||
{theme.header_image_id, "Header background"},
|
||||
{theme.icon_image_id, "Site icon"}
|
||||
]
|
||||
|
||||
count =
|
||||
Enum.reduce(mapping, 0, fn {image_id, alt}, acc ->
|
||||
if image_id do
|
||||
{updated, _} =
|
||||
from(i in Image, where: i.id == ^image_id and is_nil(i.alt))
|
||||
|> Repo.update_all(set: [alt: alt])
|
||||
|
||||
acc + updated
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
Mix.shell().info(" Updated #{count} theme image(s)")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user