defmodule BDS.UI.PostEditor.Metadata do @moduledoc false import Ecto.Query alias BDS.{I18n, Media, Metadata, PostLinks, Posts, Preview, Repo, Templates} alias BDS.Media.Media, as: MediaRecord alias BDS.Posts.{Post, PostMedia} use Gettext, backend: BDS.Gettext @spec project_metadata(term()) :: term() 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 @spec canonical_language(term(), term()) :: term() def canonical_language(post, metadata) do BDS.UI.PostEditor.Draft.normalize_language( post.language, metadata.main_language || "en" ) end @spec translations(term()) :: term() def translations(post_id) do {:ok, translations} = Posts.list_post_translations(post_id) Map.new(translations, fn translation -> {translation.language, translation} end) end @spec languages(term()) :: term() def 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 @spec template_options(term()) :: term() def template_options(project_id) do Repo.all( from template in Templates.Template, where: template.project_id == ^project_id, order_by: [asc: template.title, asc: template.slug], select: %{ slug: template.slug, title: fragment("COALESCE(?, ?)", template.title, template.slug) } ) rescue _error -> [] end @spec linked_media(term()) :: term() def linked_media(post_id) do rows = 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, pm.sort_order} ) Enum.map(rows, fn {media_id, sort_order} -> case Media.get_media(media_id) do %MediaRecord{} = media -> %{ media_id: media.id, has_thumbnail: String.starts_with?(to_string(media.mime_type || ""), "image/"), name: media.title || media.original_name || media.id, sort_order: sort_order || 0 } _other -> nil end end) |> Enum.reject(&is_nil/1) rescue _error -> [] end @spec post_links(term()) :: term() def post_links(post_id) do %{ backlinks: related_posts(PostLinks.list_incoming_links(post_id), :source_post_id), outlinks: related_posts(PostLinks.list_outgoing_links(post_id), :target_post_id) } end defp related_posts(links, key) do Enum.map(links, fn link -> case Posts.get_post(Map.fetch!(link, key)) do %Post{} = post -> %{ id: post.id, title: post.title || post.slug || post.id, text: link.link_text || post.slug || post.id } _other -> nil end end) |> Enum.reject(&is_nil/1) end @spec translation_flags(term(), term(), term(), term()) :: term() def translation_flags(post, canonical_language, active_language, translations) do canonical = %{ language: canonical_language, flag: I18n.flag(canonical_language), status: Atom.to_string(post.status || :draft), active: active_language == canonical_language, label: canonical_language } others = translations |> Map.values() |> Enum.sort_by(& &1.language) |> Enum.map(fn translation -> %{ language: translation.language, flag: I18n.flag(translation.language), status: Atom.to_string(translation.status || :draft), active: active_language == translation.language, label: translation.language } end) [canonical | others] end @spec footer(term(), term(), term(), term()) :: term() def footer(post, translation, active_language, canonical_language) do if active_language == canonical_language do %{ created_at: format_timestamp(post.created_at), updated_at: format_timestamp(post.updated_at), published_at: format_timestamp(post.published_at) } else %{ created_at: format_timestamp((translation && translation.created_at) || post.created_at), updated_at: format_timestamp((translation && translation.updated_at) || post.updated_at), published_at: format_timestamp(translation && translation.published_at) } end end defp format_timestamp(nil), do: "" defp format_timestamp(timestamp) do timestamp |> DateTime.from_unix!(:millisecond) |> Calendar.strftime("%x") end @spec display_title(term(), term(), term()) :: term() def display_title(title, slug, fallback_id) do blank_to_nil(title) || blank_to_nil(slug) || fallback_id || dgettext("ui", "Untitled") end @spec gallery_count(term()) :: term() def gallery_count(form) do content = form |> Map.get("content", "") |> to_string() image_count = content |> then(&Regex.scan(~r/!\[[^\]]*\]\([^\)]+\)/, &1)) |> length() gallery_macro_count = content |> then(&Regex.scan(~r/\[\[gallery\]\]/i, &1)) |> length() max(image_count, gallery_macro_count) end @spec preview_url(term(), term(), term(), term()) :: term() def preview_url(_post, _active_language, _canonical_language, mode) when mode != :preview, do: nil @spec preview_url(term(), term(), term(), term()) :: term() def preview_url(%Post{} = post, active_language, canonical_language, :preview) do query = %{} |> maybe_put_query("draft", "true") |> maybe_put_query("post_id", post.id) |> maybe_put_query("lang", active_language != canonical_language && active_language) Preview.base_url() <> canonical_preview_path(post.created_at, post.slug) <> "?" <> URI.encode_query(query) end defp canonical_preview_path(created_at_ms, slug) do datetime = DateTime.from_unix!(created_at_ms, :millisecond) "/#{datetime.year}/#{BDS.Strings.pad2(datetime.month)}/#{BDS.Strings.pad2(datetime.day)}/#{slug || ""}" end defp maybe_put_query(query, _key, false), do: query defp maybe_put_query(query, _key, nil), do: query defp maybe_put_query(query, key, value), do: Map.put(query, key, value) @spec truthy?(term()) :: boolean() def truthy?(value), do: BDS.Values.truthy?(value) @spec blank?(term()) :: term() def blank?(value), do: blank_to_nil(value) == nil @spec blank_to_nil(term()) :: term() def blank_to_nil(value) do value |> to_string() |> String.trim() |> case do "" -> nil trimmed -> trimmed end end end