Packages
phoenix_kit
1.7.39
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/publishing/web/editor/helpers.ex
defmodule PhoenixKit.Modules.Publishing.Web.Editor.Helpers do
@moduledoc """
Shared helper functions for the publishing editor.
Contains utilities for URL building, language handling,
virtual post creation, and other common operations.
"""
alias PhoenixKit.Modules.Publishing
alias PhoenixKit.Modules.Publishing.Storage
alias PhoenixKit.Modules.Publishing.Web.Editor.Translation
alias PhoenixKit.Modules.Publishing.Web.HTML, as: PublishingHTML
alias PhoenixKit.Modules.Storage.URLSigner
# ============================================================================
# Language Helpers
# ============================================================================
@doc """
Assigns current language with enabled/known status.
"""
def assign_current_language(socket, language_code) do
enabled_languages = socket.assigns[:all_enabled_languages] || []
lang_info = Publishing.get_language_info(language_code)
post_primary = socket.assigns[:post] && socket.assigns.post[:primary_language]
primary_language = post_primary || Storage.get_primary_language()
is_primary = language_code == primary_language
# Check if the post needs primary language migration
primary_lang_status =
case {socket.assigns[:blog_slug], socket.assigns[:post]} do
{blog_slug, post} when is_binary(blog_slug) and is_map(post) ->
post_dir = get_post_directory(post)
if post_dir do
Publishing.check_primary_language_status(blog_slug, post_dir)
else
{:ok, :current}
end
_ ->
{:ok, :current}
end
# Get language names for display
current_language_name = if lang_info, do: lang_info[:name], else: String.upcase(language_code)
primary_language_name = get_language_name(primary_language)
global_primary = Storage.get_primary_language()
global_primary_language_name = get_language_name(global_primary)
socket
|> Phoenix.Component.assign(:current_language, language_code)
|> Phoenix.Component.assign(:current_language_name, current_language_name)
|> Phoenix.Component.assign(:primary_language_name, primary_language_name)
|> Phoenix.Component.assign(:global_primary_language_name, global_primary_language_name)
|> Phoenix.Component.assign(
:current_language_enabled,
Storage.language_enabled?(language_code, enabled_languages)
)
|> Phoenix.Component.assign(:current_language_known, lang_info != nil)
|> Phoenix.Component.assign(:is_primary_language, is_primary)
|> Phoenix.Component.assign(:post_primary_language_status, primary_lang_status)
|> Translation.maybe_clear_completed_translation_status()
end
@doc """
Gets the language name for a language code.
"""
def get_language_name(language_code) do
case Publishing.get_language_info(language_code) do
%{name: name} -> name
_ -> String.upcase(language_code)
end
end
@doc """
Formats a list of language codes for display.
"""
def format_language_list(language_codes) when is_list(language_codes) do
count = length(language_codes)
cond do
count == 0 ->
""
count <= 3 ->
Enum.map_join(language_codes, ", ", &get_language_name/1)
true ->
"#{count} languages"
end
end
def format_language_list(_), do: ""
@doc """
Gets the editor language from assigns.
"""
def editor_language(assigns) do
assigns[:current_language] ||
assigns |> Map.get(:post, %{}) |> Map.get(:language) ||
hd(Storage.enabled_language_codes())
end
@doc """
Builds language data for the publishing_language_switcher component.
"""
def build_editor_languages(post, _blog_slug, enabled_languages, current_language) do
post_primary = post[:primary_language] || Storage.get_primary_language()
all_languages =
Storage.order_languages_for_display(
post.available_languages || [],
enabled_languages,
post_primary
)
language_statuses = Map.get(post, :language_statuses) || %{}
Enum.map(all_languages, fn lang_code ->
lang_info = Publishing.get_language_info(lang_code)
file_exists = lang_code in (post.available_languages || [])
is_current = lang_code == current_language
is_enabled = Storage.language_enabled?(lang_code, enabled_languages)
is_known = lang_info != nil
is_primary = lang_code == post_primary
status = Map.get(language_statuses, lang_code)
display_code = Storage.get_display_code(lang_code, enabled_languages)
%{
code: lang_code,
display_code: display_code,
name: if(lang_info, do: lang_info.name, else: lang_code),
flag: if(lang_info, do: lang_info.flag, else: ""),
status: status,
exists: file_exists,
is_current: is_current,
enabled: is_enabled,
known: is_known,
is_primary: is_primary
}
end)
end
# ============================================================================
# URL Helpers
# ============================================================================
@doc """
Builds the public URL for a post.
"""
def build_public_url(post, language) do
if Map.get(post.metadata, :status) == "published" do
build_url_for_mode(post, language)
else
nil
end
end
defp build_url_for_mode(post, language) do
blog_slug = post.group || "blog"
case Map.get(post, :mode) do
:slug -> build_slug_mode_url(blog_slug, post, language)
:timestamp -> build_timestamp_mode_url(blog_slug, post, language)
_ -> nil
end
end
defp build_slug_mode_url(blog_slug, post, language) do
if post.slug do
PublishingHTML.build_post_url(blog_slug, post, language)
else
nil
end
end
defp build_timestamp_mode_url(blog_slug, post, language) do
if post.metadata.published_at do
case DateTime.from_iso8601(post.metadata.published_at) do
{:ok, _datetime, _} -> PublishingHTML.build_post_url(blog_slug, post, language)
_ -> nil
end
else
nil
end
end
@doc """
Gets the URL for a file from storage.
"""
def get_file_url(file_id) do
URLSigner.signed_url(file_id, "original")
end
# ============================================================================
# Virtual Post Building
# ============================================================================
@doc """
Builds a virtual post for new post creation.
"""
def build_virtual_post(blog_slug, "slug", primary_language, now) do
%{
group: blog_slug,
date: nil,
time: nil,
path: nil,
full_path: nil,
metadata: %{
title: "",
status: "draft",
published_at: DateTime.to_iso8601(now),
slug: "",
featured_image_id: nil
},
content: "",
language: primary_language,
available_languages: [],
mode: :slug,
slug: nil,
is_legacy_structure: false
}
end
def build_virtual_post(blog_slug, _mode, primary_language, now) do
date = DateTime.to_date(now)
time = DateTime.to_time(now)
time_folder =
"#{String.pad_leading(to_string(time.hour), 2, "0")}:#{String.pad_leading(to_string(time.minute), 2, "0")}"
%{
group: blog_slug,
date: date,
time: time,
path:
Path.join([
blog_slug,
Date.to_iso8601(date),
time_folder,
"#{primary_language}.phk"
]),
full_path: nil,
metadata: %{
title: "",
status: "draft",
published_at: DateTime.to_iso8601(now),
featured_image_id: nil
},
content: "",
language: primary_language,
available_languages: [],
mode: :timestamp,
is_legacy_structure: false
}
end
@doc """
Builds a virtual translation for a new language.
"""
def build_virtual_translation(post, blog_slug, new_language, new_path, socket) do
post
|> Map.put(:path, new_path)
|> Map.put(:language, new_language)
|> Map.put(:blog, blog_slug || "blog")
|> Map.put(:content, "")
|> Map.put(:metadata, Map.put(post.metadata, :title, ""))
|> Map.put(:mode, post.mode)
|> Map.put(:slug, post.slug || Map.get(socket.assigns.form, "slug"))
end
# ============================================================================
# Post Directory Helpers
# ============================================================================
@doc """
Gets post directory path for primary language status check.
"""
def get_post_directory(%{mode: :timestamp, date: date, time: time})
when not is_nil(date) and not is_nil(time) do
date_str = Date.to_iso8601(date)
time_str = format_time_for_path(time)
Path.join(date_str, time_str)
end
def get_post_directory(%{slug: slug}) when is_binary(slug) and slug != "", do: slug
def get_post_directory(_), do: nil
defp format_time_for_path(%Time{} = time) do
time
|> Time.to_string()
|> String.slice(0, 5)
|> String.replace(":", ":")
end
defp format_time_for_path(time) when is_binary(time), do: String.slice(time, 0, 5)
defp format_time_for_path(_), do: nil
@doc """
Gets the base directory for a slug-mode post.
"""
def slug_base_dir(post, blog_slug) do
cond do
# For versioned posts, use the path to preserve version directory
post.path && not Map.get(post, :is_legacy_structure, false) ->
Path.dirname(post.path)
# For legacy slug mode posts without versioning
Map.get(post, :mode) == :slug and Map.get(post, :slug) ->
Path.join([blog_slug || "blog", post.slug])
# Fallback to path dirname if available
post.path ->
Path.dirname(post.path)
true ->
Path.join([blog_slug || "blog", post.slug || ""])
end
end
# ============================================================================
# Featured Image Helpers
# ============================================================================
@doc """
Gets the preview URL for a featured image.
"""
def featured_image_preview_url(value) do
case sanitize_featured_image_id(value) do
nil ->
nil
file_id ->
PublishingHTML.featured_image_url(%{metadata: %{featured_image_id: file_id}}, "medium")
end
end
@doc """
Sanitizes a featured image ID value.
"""
def sanitize_featured_image_id(value) when is_binary(value) do
value
|> String.trim()
|> case do
"" -> nil
trimmed -> trimmed
end
end
def sanitize_featured_image_id(_), do: nil
end