defmodule BDS.Desktop.ShellLive.OverlayComponents do @moduledoc false use Phoenix.Component use Gettext, backend: BDS.Gettext import Ecto.Query alias BDS.{I18n, Media, Metadata, Posts, Repo} alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Translation, as: MediaTranslation alias BDS.Posts.{Post, PostMedia, Translation} alias BDS.Tags.Tag embed_templates("overlay_html/*") def context(assigns, tab_title, tab_subtitle) do project_id = assigns.projects.active_project_id metadata = project_metadata(project_id) current_tab = assigns.current_tab page_language = assigns.page_language posts = posts(project_id) media = media(project_id) %{ current_tab: %{ type: current_tab.type, id: current_tab.id, title: tab_title, subtitle: tab_subtitle }, current_post_language: source_language(current_tab, metadata), current_media_language: source_language(current_tab, metadata), posts: posts, media: media, post_media_ids: post_media_ids(current_tab), blog_languages: blog_languages(metadata), language_names: language_names(), language_flags: language_flags(), existing_translations: existing_translations(current_tab), ai_title: BDS.Gettext.lgettext(page_language, "ui", "AI Suggestions"), insert_link_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Link"), insert_media_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Media"), language_picker_title: BDS.Gettext.lgettext(page_language, "ui", "Translate"), gallery_title: tab_title, ai_fields: ai_fields(current_tab, tab_title, tab_subtitle, page_language), delete_details: delete_details(current_tab, page_language), merge_details: merge_details(project_id, page_language) } end def kind("ai_suggestions"), do: :ai_suggestions def kind("insert_link"), do: :insert_link def kind("insert_media"), do: :insert_media def kind("language_picker"), do: :language_picker def kind("confirm_delete"), do: :confirm_delete def kind("confirm_merge"), do: :confirm_merge def kind("gallery"), do: :gallery def kind(_kind), do: nil def tab("internal"), do: :internal def tab("external"), do: :external def tab(_tab), do: :internal def markdown_link(text, url), do: "[#{text}](#{url})" def project_metadata(nil), do: %{main_language: "en", blog_languages: []} def project_metadata(project_id) do {:ok, metadata} = Metadata.get_project_metadata(project_id) metadata rescue _error -> %{main_language: "en", blog_languages: []} end defp posts(nil), do: [] defp posts(project_id) do Repo.all( from post in Post, where: post.project_id == ^project_id, order_by: [desc: post.updated_at, desc: post.created_at], select: %{ id: post.id, title: post.title, slug: post.slug, status: post.status, published_at: post.published_at, updated_at: post.updated_at, language: post.language } ) |> Enum.map(fn post -> %{ id: post.id, title: post.title || post.slug || post.id, status: Atom.to_string(post.status || :draft), canonical_url: canonical_post_url(post) } end) end defp media(nil), do: [] defp media(project_id) do Repo.all( from media in MediaRecord, where: media.project_id == ^project_id, order_by: [desc: media.updated_at, desc: media.created_at], select: %{ id: media.id, title: media.title, original_name: media.original_name, mime_type: media.mime_type, alt: media.alt, caption: media.caption } ) |> Enum.map(fn media -> %{ id: media.id, title: media.title || media.original_name || media.id, original_name: media.original_name || media.id, is_image: String.starts_with?(to_string(media.mime_type || ""), "image/"), thumbnail_url: "/media-thumbnail/#{media.id}", image_url: "/media-thumbnail/#{media.id}?size=large", alt_text: media.alt || media.caption || media.title } end) end defp post_media_ids(%{type: :post, id: post_id}) do Repo.all( from pm in PostMedia, where: pm.post_id == ^post_id, order_by: [asc: pm.sort_order, asc: pm.media_id], select: pm.media_id ) rescue _error -> [] end defp post_media_ids(_tab), do: [] defp existing_translations(%{type: :post, id: post_id}) do Repo.all( from translation in Translation, where: translation.translation_for == ^post_id, select: {translation.language, translation.status} ) |> Map.new(fn {language, status} -> {language, Atom.to_string(status || :draft)} end) rescue _error -> %{} end defp existing_translations(%{type: :media, id: media_id}) do Repo.all( from translation in MediaTranslation, where: translation.translation_for == ^media_id, select: {translation.language, "draft"} ) |> Map.new(fn {language, status} -> {language, status} end) rescue _error -> %{} end defp existing_translations(_tab), do: %{} defp blog_languages(metadata) do ([metadata.main_language || "en"] ++ (metadata.blog_languages || []) ++ Enum.map(I18n.supported_languages(), & &1.code)) |> Enum.reject(&is_nil/1) |> Enum.uniq() end defp source_language(%{type: :post, id: post_id}, metadata) do case Posts.get_post(post_id) do %Post{language: language} when is_binary(language) and language != "" -> language _other -> metadata.main_language || "en" end rescue _error -> metadata.main_language || "en" end defp source_language(%{type: :media, id: media_id}, metadata) do case Media.get_media(media_id) do %MediaRecord{language: language} when is_binary(language) and language != "" -> language _other -> metadata.main_language || "en" end rescue _error -> metadata.main_language || "en" end defp source_language(_tab, metadata), do: metadata.main_language || "en" defp language_names do %{ "en" => "English", "de" => "Deutsch", "fr" => "Francais", "it" => "Italiano", "es" => "Espanol" } end defp language_flags do I18n.supported_languages() |> Enum.into(%{}, fn language -> {language.code, I18n.flag(language.code)} end) end defp ai_fields(%{type: :post, id: post_id}, title, subtitle, page_language) do case Posts.get_post(post_id) do %Post{} = post -> [ %{ key: "title", label: BDS.Gettext.lgettext(page_language, "ui", "Title"), current_value: post.title || title, suggested_value: "", locked: false, loading: true }, %{ key: "excerpt", label: BDS.Gettext.lgettext(page_language, "ui", "Excerpt"), current_value: post.excerpt || subtitle, suggested_value: "", locked: false, loading: true }, %{ key: "slug", label: BDS.Gettext.lgettext(page_language, "ui", "Slug"), current_value: post.slug || slugify(post.title || title), suggested_value: "", locked: post.status == :published, loading: true } ] _other -> [] end rescue _error -> [] end defp ai_fields(%{type: :media, id: media_id}, title, _subtitle, page_language) do case Media.get_media(media_id) do %MediaRecord{} = media -> [ %{ key: "title", label: BDS.Gettext.lgettext(page_language, "ui", "Title"), current_value: media.title || title, suggested_value: "", locked: false, loading: true }, %{ key: "alt", label: BDS.Gettext.lgettext(page_language, "ui", "Alt Text"), current_value: media.alt || "", suggested_value: "", locked: false, loading: true }, %{ key: "caption", label: BDS.Gettext.lgettext(page_language, "ui", "Caption"), current_value: media.caption || "", suggested_value: "", locked: false, loading: true } ] _other -> [] end rescue _error -> [] end defp ai_fields(_tab, _title, _subtitle, _page_language), do: [] defp delete_details(%{type: :media, id: media_id}, page_language) do entity_name = case Media.get_media(media_id) do %MediaRecord{} = media -> media.title || media.original_name || media.id _other -> media_id end reference_list = Repo.all( from post in Post, join: pm in PostMedia, on: pm.post_id == post.id, where: pm.media_id == ^media_id, order_by: [asc: pm.sort_order, desc: post.updated_at], select: post.title ) |> Enum.map(&(&1 || media_id)) %{ title: BDS.Gettext.lgettext(page_language, "ui", "Delete Media"), entity_name: entity_name, entity_type: "media", reference_list: reference_list } rescue _error -> %{ title: BDS.Gettext.lgettext(page_language, "ui", "Delete Media"), entity_name: media_id, entity_type: "media", reference_list: [] } end defp delete_details(%{type: :tags}, page_language) do tag_name = Repo.one(from tag in Tag, order_by: [asc: tag.name], limit: 1, select: tag.name) |> Kernel.||("tag") %{ title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"), entity_name: tag_name, entity_type: "tag", reference_list: [] } rescue _error -> %{ title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"), entity_name: "tag", entity_type: "tag", reference_list: [] } end defp delete_details(_tab, page_language) do %{ title: BDS.Gettext.lgettext(page_language, "ui", "Delete"), entity_name: "", entity_type: "item", reference_list: [] } end defp merge_details(project_id, page_language) do tags = Repo.all( from tag in Tag, where: tag.project_id == ^project_id, order_by: [asc: tag.name], limit: 3, select: tag.name ) target = List.first(tags) || "tag" %{ target: target, count: max(length(tags), 1), title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"), message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.") } rescue _error -> %{ target: "tag", count: 1, title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"), message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.") } end defp canonical_post_url(post) do timestamp = post.published_at || post.updated_at || System.system_time(:millisecond) date = DateTime.from_unix!(timestamp, :millisecond) "/#{date.year}/#{pad2(date.month)}/#{pad2(date.day)}/#{post.slug || post.id}" end defp pad2(value), do: value |> Integer.to_string() |> String.pad_leading(2, "0") defp slugify(value) do value |> to_string() |> String.downcase() |> String.replace(~r/[^a-z0-9]+/u, "-") |> String.trim("-") end end