338 lines
9.8 KiB
Elixir
338 lines
9.8 KiB
Elixir
defmodule BDS.Desktop.Overlay do
|
|
@moduledoc false
|
|
|
|
def open(:post, :ai_suggestions, context) do
|
|
%{
|
|
kind: :ai_suggestions,
|
|
title: Map.get(context, :ai_title, "AI Suggestions"),
|
|
fields: normalize_ai_fields(Map.get(context, :ai_fields, []))
|
|
}
|
|
end
|
|
|
|
def open(:media, :ai_suggestions, context), do: open(:post, :ai_suggestions, context)
|
|
|
|
def open(:post, :insert_link, context) do
|
|
posts = related_posts(Map.get(context, :posts, []), current_id(context))
|
|
|
|
%{
|
|
kind: :insert_link,
|
|
title: Map.get(context, :insert_link_title, "Insert Link"),
|
|
active_tab: :internal,
|
|
search_query: "",
|
|
external_url: "",
|
|
external_text: current_title(context),
|
|
results: [],
|
|
related_posts: Enum.map(Enum.take(posts, 5), &to_insert_link_result/1),
|
|
all_posts: posts
|
|
}
|
|
end
|
|
|
|
def open(:post, :insert_media, context) do
|
|
media = Map.get(context, :media, [])
|
|
|
|
%{
|
|
kind: :insert_media,
|
|
title: Map.get(context, :insert_media_title, "Insert Media"),
|
|
search_query: "",
|
|
results: Enum.map(media, &to_insert_media_result/1),
|
|
all_media: media
|
|
}
|
|
end
|
|
|
|
def open(:post, :language_picker, context) do
|
|
language_picker(context, Map.get(context, :current_post_language, "en"))
|
|
end
|
|
|
|
def open(:media, :language_picker, context) do
|
|
language_picker(context, Map.get(context, :current_media_language, "en"))
|
|
end
|
|
|
|
def open(:media, :confirm_delete, context) do
|
|
delete_details = Map.get(context, :delete_details, %{})
|
|
|
|
%{
|
|
kind: :confirm_delete,
|
|
title: Map.get(delete_details, :title, "Delete"),
|
|
entity_name: Map.get(delete_details, :entity_name, ""),
|
|
entity_type: Map.get(delete_details, :entity_type, "media"),
|
|
reference_count: length(Map.get(delete_details, :reference_list, [])),
|
|
reference_list: Map.get(delete_details, :reference_list, [])
|
|
}
|
|
end
|
|
|
|
def open(:tags, :confirm_delete, context), do: open(:media, :confirm_delete, context)
|
|
|
|
def open(:tags, :confirm_merge, context) do
|
|
merge = Map.get(context, :merge_details, %{})
|
|
target = Map.get(merge, :target, "")
|
|
count = Map.get(merge, :count, 0)
|
|
|
|
%{
|
|
kind: :confirm_dialog,
|
|
title: Map.get(merge, :title, "Merge #{count} tags into #{target}?"),
|
|
message: Map.get(merge, :message, "Cannot be undone.")
|
|
}
|
|
end
|
|
|
|
def open(:post, :gallery, context) do
|
|
images =
|
|
context
|
|
|> gallery_images()
|
|
|> Enum.map(&to_gallery_image/1)
|
|
|
|
%{
|
|
kind: :gallery,
|
|
title: Map.get(context, :gallery_title, current_title(context)),
|
|
post_id: current_id(context),
|
|
images: images,
|
|
lightbox: nil
|
|
}
|
|
end
|
|
|
|
def open(_route, _action, _context), do: nil
|
|
|
|
def set_search_query(%{kind: :insert_link} = overlay, query) do
|
|
normalized = normalize_query(query)
|
|
|
|
results =
|
|
if String.length(normalized) < 2 do
|
|
[]
|
|
else
|
|
overlay
|
|
|> Map.get(:all_posts, [])
|
|
|> Enum.filter(&search_matches?(&1.title, normalized))
|
|
|> Enum.map(&to_insert_link_result/1)
|
|
end
|
|
|
|
%{overlay | search_query: normalized, results: results}
|
|
end
|
|
|
|
def set_search_query(%{kind: :insert_media} = overlay, query) do
|
|
normalized = normalize_query(query)
|
|
|
|
results =
|
|
overlay
|
|
|> Map.get(:all_media, [])
|
|
|> Enum.filter(fn media ->
|
|
normalized == "" or
|
|
search_matches?(Map.get(media, :title, ""), normalized) or
|
|
search_matches?(Map.get(media, :original_name, ""), normalized)
|
|
end)
|
|
|> Enum.map(&to_insert_media_result/1)
|
|
|
|
%{overlay | search_query: normalized, results: results}
|
|
end
|
|
|
|
def set_search_query(overlay, _query), do: overlay
|
|
|
|
def set_active_tab(%{kind: :insert_link} = overlay, tab) when tab in [:internal, :external] do
|
|
%{overlay | active_tab: tab}
|
|
end
|
|
|
|
def set_active_tab(overlay, _tab), do: overlay
|
|
|
|
def update_form_value(%{kind: :insert_link} = overlay, :external_url, value) do
|
|
%{overlay | external_url: normalize_query(value)}
|
|
end
|
|
|
|
def update_form_value(%{kind: :insert_link} = overlay, :external_text, value) do
|
|
%{overlay | external_text: to_string(value || "")}
|
|
end
|
|
|
|
def update_form_value(overlay, _key, _value), do: overlay
|
|
|
|
def toggle_ai_field(%{kind: :ai_suggestions} = overlay, key) do
|
|
fields =
|
|
Enum.map(overlay.fields, fn field ->
|
|
if field.key == key and not field.locked do
|
|
%{field | accepted: not field.accepted}
|
|
else
|
|
field
|
|
end
|
|
end)
|
|
|
|
%{overlay | fields: fields}
|
|
end
|
|
|
|
def toggle_ai_field(overlay, _key), do: overlay
|
|
|
|
def select_gallery_image(%{kind: :gallery} = overlay, media_id) do
|
|
case Enum.find_index(overlay.images, &(&1.media_id == media_id)) do
|
|
nil -> overlay
|
|
index -> %{overlay | lightbox: lightbox_from_index(overlay.images, index)}
|
|
end
|
|
end
|
|
|
|
def select_gallery_image(overlay, _media_id), do: overlay
|
|
|
|
def close_lightbox(%{kind: :gallery} = overlay), do: %{overlay | lightbox: nil}
|
|
def close_lightbox(overlay), do: overlay
|
|
|
|
def lightbox_next(%{kind: :gallery, lightbox: lightbox, images: images} = overlay)
|
|
when is_map(lightbox) and images != [] do
|
|
next_index = rem(lightbox.current_index + 1, length(images))
|
|
%{overlay | lightbox: lightbox_from_index(images, next_index)}
|
|
end
|
|
|
|
def lightbox_next(overlay), do: overlay
|
|
|
|
def lightbox_previous(%{kind: :gallery, lightbox: lightbox, images: images} = overlay)
|
|
when is_map(lightbox) and images != [] do
|
|
next_index = rem(lightbox.current_index - 1 + length(images), length(images))
|
|
%{overlay | lightbox: lightbox_from_index(images, next_index)}
|
|
end
|
|
|
|
def lightbox_previous(overlay), do: overlay
|
|
|
|
def selected_ai_fields(%{kind: :ai_suggestions, fields: fields}) do
|
|
Enum.filter(fields, & &1.accepted)
|
|
end
|
|
|
|
def selected_ai_fields(_overlay), do: []
|
|
|
|
def insert_link_result(%{kind: :insert_link} = overlay, post_id) do
|
|
Enum.find(overlay.results ++ overlay.related_posts, &(&1.post_id == post_id))
|
|
end
|
|
|
|
def insert_link_result(_overlay, _post_id), do: nil
|
|
|
|
def insert_media_result(%{kind: :insert_media} = overlay, media_id) do
|
|
Enum.find(overlay.results, &(&1.media_id == media_id))
|
|
end
|
|
|
|
def insert_media_result(_overlay, _media_id), do: nil
|
|
|
|
defp language_picker(context, source_language) do
|
|
targets =
|
|
context
|
|
|> Map.get(:blog_languages, [])
|
|
|> Enum.uniq()
|
|
|> Enum.reject(&(&1 == source_language))
|
|
|> Enum.map(fn code ->
|
|
existing_status = Map.get(Map.get(context, :existing_translations, %{}), code)
|
|
|
|
%{
|
|
code: code,
|
|
name: Map.get(Map.get(context, :language_names, %{}), code, String.upcase(code)),
|
|
flag_emoji: Map.get(Map.get(context, :language_flags, %{}), code, code),
|
|
has_existing_translation: not is_nil(existing_status),
|
|
existing_status: existing_status
|
|
}
|
|
end)
|
|
|
|
%{
|
|
kind: :language_picker,
|
|
title: Map.get(context, :language_picker_title, "Translate"),
|
|
source_language: source_language,
|
|
available_targets: targets
|
|
}
|
|
end
|
|
|
|
def set_ai_suggestions(%{kind: :ai_suggestions} = overlay, suggestions) do
|
|
fields =
|
|
Enum.map(overlay.fields, fn field ->
|
|
case Map.get(suggestions, field.key) do
|
|
nil ->
|
|
field
|
|
|
|
value when is_binary(value) and value != "" ->
|
|
%{field | suggested_value: value, loading: false}
|
|
|
|
_other ->
|
|
field
|
|
end
|
|
end)
|
|
|
|
%{overlay | fields: fields}
|
|
end
|
|
|
|
def set_ai_suggestions(overlay, _suggestions), do: overlay
|
|
|
|
defp normalize_ai_fields(fields) do
|
|
Enum.map(fields, fn field ->
|
|
%{
|
|
key: to_string(Map.get(field, :key, "")),
|
|
label: Map.get(field, :label, ""),
|
|
current_value: Map.get(field, :current_value, ""),
|
|
suggested_value: Map.get(field, :suggested_value, ""),
|
|
accepted: not Map.get(field, :locked, false),
|
|
locked: Map.get(field, :locked, false),
|
|
loading: Map.get(field, :loading, false)
|
|
}
|
|
end)
|
|
end
|
|
|
|
defp current_id(context), do: get_in(context, [:current_tab, :id])
|
|
defp current_title(context), do: get_in(context, [:current_tab, :title]) || ""
|
|
|
|
defp related_posts(posts, current_post_id) do
|
|
Enum.reject(posts, &(&1.id == current_post_id))
|
|
end
|
|
|
|
defp gallery_images(context) do
|
|
images = Enum.filter(Map.get(context, :media, []), &Map.get(&1, :is_image, false))
|
|
post_media_ids = Map.get(context, :post_media_ids, [])
|
|
|
|
case Enum.filter(images, &(&1.id in post_media_ids)) do
|
|
[] -> images
|
|
linked -> linked
|
|
end
|
|
end
|
|
|
|
defp to_insert_link_result(post) do
|
|
%{
|
|
post_id: post.id,
|
|
title: post.title,
|
|
status: to_string(Map.get(post, :status, "draft")),
|
|
canonical_url: Map.get(post, :canonical_url, "/posts/#{post.id}"),
|
|
similarity_score: Map.get(post, :similarity_score)
|
|
}
|
|
end
|
|
|
|
defp to_insert_media_result(media) do
|
|
%{
|
|
media_id: media.id,
|
|
title: Map.get(media, :title, ""),
|
|
original_name: Map.get(media, :original_name, media.id),
|
|
is_image: Map.get(media, :is_image, false),
|
|
thumbnail_url: Map.get(media, :thumbnail_url)
|
|
}
|
|
end
|
|
|
|
defp to_gallery_image(media) do
|
|
%{
|
|
media_id: media.id,
|
|
thumbnail_url: Map.get(media, :thumbnail_url),
|
|
image_url: Map.get(media, :image_url, Map.get(media, :thumbnail_url)),
|
|
alt_text: Map.get(media, :alt_text),
|
|
title: Map.get(media, :title, Map.get(media, :original_name, media.id))
|
|
}
|
|
end
|
|
|
|
defp lightbox_from_index(images, index) do
|
|
image = Enum.at(images, index)
|
|
|
|
%{
|
|
current_index: index,
|
|
total_count: length(images),
|
|
media_id: image.media_id,
|
|
image_url: image.image_url,
|
|
alt_text: image.alt_text,
|
|
title: image.title
|
|
}
|
|
end
|
|
|
|
defp search_matches?(value, query) do
|
|
value
|
|
|> to_string()
|
|
|> String.downcase()
|
|
|> String.contains?(String.downcase(query))
|
|
end
|
|
|
|
defp normalize_query(value) do
|
|
value
|
|
|> to_string()
|
|
|> String.trim()
|
|
end
|
|
end
|