Packages

phoenix_kit

1.7.31
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
phoenix_kit lib modules publishing web new.ex
Raw

lib/modules/publishing/web/new.ex

defmodule PhoenixKit.Modules.Publishing.Web.New do
@moduledoc """
LiveView for creating a new blog workspace (publishing group).
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.Publishing
alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub
alias PhoenixKit.Modules.Publishing.Storage
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Routes
# Preset types with their default item names
@preset_types Publishing.preset_types()
def mount(_params, _session, socket) do
{initial_params, auto_slug?, last_generated_slug, auto_item_names?} =
normalize_form_params(%{}, true, "", true)
socket =
socket
|> assign(:project_title, Settings.get_project_title())
|> assign(:page_title, gettext("Create Publishing Group"))
|> assign(
:current_path,
Routes.path("/admin/settings/publishing/new")
)
|> assign(:slug_autogenerated?, auto_slug?)
|> assign(:last_generated_slug, last_generated_slug)
|> assign(:item_names_autogenerated?, auto_item_names?)
|> assign(:form_params, initial_params)
|> assign(:form, new_blog_form(initial_params))
|> assign(:preset_types, @preset_types)
|> assign(:enabled_languages, Storage.enabled_language_codes())
|> assign(:endpoint_url, nil)
{:ok, socket}
end
def handle_params(_params, uri, socket) do
endpoint_url = extract_endpoint_url(uri)
{:noreply, assign(socket, :endpoint_url, endpoint_url)}
end
def handle_event("update_new_group", %{"blog" => params}, socket) do
{normalized_params, auto_slug?, last_generated_slug, auto_item_names?} =
normalize_form_params(
params,
socket.assigns.slug_autogenerated?,
socket.assigns.last_generated_slug,
socket.assigns.item_names_autogenerated?
)
{:noreply,
socket
|> assign(:slug_autogenerated?, auto_slug?)
|> assign(:last_generated_slug, last_generated_slug)
|> assign(:item_names_autogenerated?, auto_item_names?)
|> assign(:form_params, normalized_params)
|> assign(:form, new_blog_form(normalized_params))}
end
def handle_event("manual_slug", %{"blog" => %{"slug" => value}}, socket) do
current_params = socket.assigns.form_params
name = Map.get(current_params, "name", "")
auto_slug = if name == "", do: "", else: Publishing.slugify(name)
trimmed_value = value |> to_string() |> String.trim()
cond do
# Empty slug - revert to auto-generated
trimmed_value == "" ->
updated = Map.put(current_params, "slug", auto_slug)
{:noreply,
socket
|> assign(:slug_autogenerated?, true)
|> assign(:last_generated_slug, auto_slug)
|> assign(:form_params, updated)
|> assign(:form, new_blog_form(updated))}
# Manually entered slug matches auto-generated - keep autogenerated flag
trimmed_value == auto_slug ->
updated = Map.put(current_params, "slug", auto_slug)
{:noreply,
socket
|> assign(:slug_autogenerated?, true)
|> assign(:last_generated_slug, auto_slug)
|> assign(:form_params, updated)
|> assign(:form, new_blog_form(updated))}
# Any other manual slug - just store it as-is (validation happens on submit)
true ->
updated = Map.put(current_params, "slug", trimmed_value)
{:noreply,
socket
|> assign(:slug_autogenerated?, false)
|> assign(:last_generated_slug, trimmed_value)
|> assign(:form_params, updated)
|> assign(:form, new_blog_form(updated))
|> clear_flash()}
end
end
def handle_event("add_group", %{"blog" => params}, socket) do
# Use raw params for validation - don't normalize/sanitize
name = params |> Map.get("name", "") |> String.trim()
mode = params |> Map.get("mode", "timestamp")
slug = params |> Map.get("slug", "") |> String.trim()
custom_type = params |> Map.get("custom_type", "") |> String.trim()
type =
if custom_type != "" do
custom_type
else
params |> Map.get("type", "blogging")
end
item_singular = params |> Map.get("item_singular", "") |> String.trim()
item_plural = params |> Map.get("item_plural", "") |> String.trim()
# Still normalize for UI updates in case of error
{normalized_params, auto_slug?, last_generated_slug, auto_item_names?} =
normalize_form_params(
params,
socket.assigns.slug_autogenerated?,
socket.assigns.last_generated_slug,
socket.assigns.item_names_autogenerated?
)
# Pass nil if slug is empty to trigger auto-generation
slug_to_validate = if slug == "", do: nil, else: slug
# Build options for add_blog
opts =
[mode: mode, slug: slug_to_validate, type: type]
|> maybe_add_opt(:item_singular, item_singular)
|> maybe_add_opt(:item_plural, item_plural)
case Publishing.add_group(name, opts) do
{:ok, group} ->
# Broadcast group created for live dashboard updates
PublishingPubSub.broadcast_group_created(group)
{:noreply,
socket
|> put_flash(:info, gettext("Publishing group \"%{name}\" created", name: group["name"]))
|> push_navigate(to: Routes.path("/admin/settings/publishing"))}
{:error, :already_exists} ->
{:noreply,
socket
|> assign_form_state(
normalized_params,
auto_slug?,
last_generated_slug,
auto_item_names?
)
|> put_flash(:error, gettext("That publishing group already exists"))}
{:error, :invalid_name} ->
{:noreply,
socket
|> assign_form_state(
normalized_params,
auto_slug?,
last_generated_slug,
auto_item_names?
)
|> put_flash(:error, gettext("Please enter a valid name"))}
{:error, :invalid_mode} ->
{:noreply,
socket
|> assign_form_state(
normalized_params,
auto_slug?,
last_generated_slug,
auto_item_names?
)
|> put_flash(:error, gettext("Invalid storage mode"))}
{:error, :invalid_type} ->
{:noreply,
socket
|> assign_form_state(
normalized_params,
auto_slug?,
last_generated_slug,
auto_item_names?
)
|> put_flash(:error, gettext("Invalid content type"))}
{:error, :invalid_slug} ->
{:noreply,
socket
|> assign_form_state(
normalized_params,
auto_slug?,
last_generated_slug,
auto_item_names?
)
|> put_flash(
:error,
gettext(
"Invalid slug format. Please use only lowercase letters, numbers, and hyphens (e.g. my-blog-name)"
)
)}
{:error, _reason} ->
{:noreply,
socket
|> assign_form_state(
normalized_params,
auto_slug?,
last_generated_slug,
auto_item_names?
)
|> put_flash(:error, gettext("Failed to create publishing group"))}
end
end
# When type changes via radio button click, reset item names to auto-fill mode
def handle_event("type_changed", %{"type" => new_type}, socket) do
current_params = socket.assigns.form_params
{default_singular, default_plural} = get_default_item_names(new_type)
updated_params =
current_params
|> Map.put("type", new_type)
|> Map.put("item_singular", default_singular)
|> Map.put("item_plural", default_plural)
{:noreply,
socket
|> assign(:item_names_autogenerated?, true)
|> assign(:form_params, updated_params)
|> assign(:form, new_blog_form(updated_params))}
end
# Also handle when type comes through the form
def handle_event("type_changed", %{"blog" => %{"type" => new_type}}, socket) do
handle_event("type_changed", %{"type" => new_type}, socket)
end
# When user manually edits item names, disable auto-fill
def handle_event("item_name_changed", _params, socket) do
{:noreply, assign(socket, :item_names_autogenerated?, false)}
end
def handle_event("cancel", _params, socket) do
{:noreply, push_navigate(socket, to: Routes.path("/admin/settings/publishing"))}
end
# Helper to assign form state after an error
defp assign_form_state(socket, params, auto_slug?, last_generated_slug, auto_item_names?) do
socket
|> assign(:slug_autogenerated?, auto_slug?)
|> assign(:last_generated_slug, last_generated_slug)
|> assign(:item_names_autogenerated?, auto_item_names?)
|> assign(:form_params, params)
|> assign(:form, new_blog_form(params))
end
# Helper to add optional params only if non-empty
defp maybe_add_opt(opts, _key, ""), do: opts
defp maybe_add_opt(opts, key, value), do: Keyword.put(opts, key, value)
defp new_blog_form(params) when is_map(params) do
defaults = %{
"name" => "",
"slug" => "",
"mode" => "timestamp",
"type" => "blogging",
"item_singular" => "post",
"item_plural" => "posts"
}
values = Map.merge(defaults, params)
to_form(values, as: :blog)
end
# Normalize form params with auto-fill behavior for slug and item names
defp normalize_form_params(
params,
slug_autogenerated?,
last_generated_slug,
item_names_autogenerated?
) do
name = normalize_name(params)
mode = normalize_mode(params)
type = normalize_type(params)
{slug_value, new_auto_slug?, new_last_generated} =
normalize_slug(params, name, slug_autogenerated?, last_generated_slug)
{item_singular, item_plural, new_auto_item_names?} =
normalize_item_names(params, type, item_names_autogenerated?)
result_params = %{
"name" => name,
"slug" => slug_value,
"mode" => mode,
"type" => type,
"item_singular" => item_singular,
"item_plural" => item_plural
}
{result_params, new_auto_slug?, new_last_generated, new_auto_item_names?}
end
defp normalize_name(params) do
params
|> Map.get("name", "")
|> to_string()
|> String.trim()
end
defp normalize_mode(params) do
params
|> Map.get("mode", "timestamp")
|> to_string()
|> String.downcase()
|> case do
"slug" -> "slug"
_ -> "timestamp"
end
end
defp normalize_type(params) do
custom_type = params |> Map.get("custom_type", "") |> to_string() |> String.trim()
if custom_type != "" do
String.downcase(custom_type)
else
params
|> Map.get("type", "blogging")
|> to_string()
|> String.downcase()
end
end
defp normalize_slug(params, name, slug_autogenerated?, last_generated_slug) do
trimmed_slug = params |> Map.get("slug", "") |> to_string() |> String.trim()
auto_slug = if name == "", do: "", else: Publishing.slugify(name)
if slug_autogenerated? do
{auto_slug, true, auto_slug}
else
slug_val = if trimmed_slug == "", do: auto_slug, else: trimmed_slug
auto? = slug_val == auto_slug
new_last = if auto?, do: slug_val, else: last_generated_slug
{slug_val, auto?, new_last}
end
end
defp normalize_item_names(params, type, item_names_autogenerated?) do
{default_singular, default_plural} = get_default_item_names(type)
item_singular_input = params |> Map.get("item_singular", "") |> to_string() |> String.trim()
item_plural_input = params |> Map.get("item_plural", "") |> to_string() |> String.trim()
if item_names_autogenerated? do
{default_singular, default_plural, true}
else
singular = if item_singular_input == "", do: default_singular, else: item_singular_input
plural = if item_plural_input == "", do: default_plural, else: item_plural_input
auto? = singular == default_singular and plural == default_plural
{singular, plural, auto?}
end
end
# Get default item names for a content type
defp get_default_item_names(type) do
case Enum.find(@preset_types, fn p -> p.type == type end) do
%{item_singular: singular, item_plural: plural} -> {singular, plural}
nil -> {"item", "items"}
end
end
defp extract_endpoint_url(uri) when is_binary(uri) do
case URI.parse(uri) do
%URI{scheme: scheme, host: host, port: port} when not is_nil(scheme) and not is_nil(host) ->
port_string = if port in [80, 443], do: "", else: ":#{port}"
"#{scheme}://#{host}#{port_string}"
_ ->
""
end
end
defp extract_endpoint_url(_), do: ""
end