Packages

phoenix_kit

1.7.79
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 editor persistence.ex
Raw

lib/modules/publishing/web/editor/persistence.ex

defmodule PhoenixKit.Modules.Publishing.Web.Editor.Persistence do
@moduledoc """
Post persistence operations for the publishing editor.
Handles create, update, and save operations for posts,
including version creation and translation saving.
"""
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.Publishing
alias PhoenixKit.Modules.Publishing.ListingCache
alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub
alias PhoenixKit.Modules.Publishing.Renderer
alias PhoenixKit.Modules.Publishing.Web.Editor.Forms
alias PhoenixKit.Modules.Publishing.Web.Editor.Helpers
require Logger
# ============================================================================
# Save Orchestration
# ============================================================================
@doc """
Performs save operation with validation and routing.
Returns {:noreply, socket}.
"""
def perform_save(socket) do
is_autosaving = Map.get(socket.assigns, :is_autosaving, false)
title = (socket.assigns.form["title"] || "") |> String.trim()
slug = (socket.assigns.form["slug"] || "") |> String.trim()
cond do
title == "" ->
if is_autosaving do
{:noreply, socket}
else
{:noreply,
Phoenix.LiveView.put_flash(socket, :warning, gettext("Title is required to save."))}
end
socket.assigns.group_mode == "slug" and slug == "" ->
if is_autosaving do
{:noreply, socket}
else
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:warning,
gettext(
"Slug is required. Enter a title to auto-generate one, or type a slug manually."
)
)}
end
true ->
do_perform_save_with_params(socket)
end
end
defp do_perform_save_with_params(socket) do
params =
socket.assigns.form
|> Map.take(["status", "published_at", "slug", "featured_image_uuid", "url_slug", "title"])
|> Map.put("content", socket.assigns.content)
params =
case {socket.assigns.group_mode, Map.get(params, "slug")} do
{"slug", slug} when is_binary(slug) and slug != "" ->
params
{"slug", _} ->
Map.delete(params, "slug")
_ ->
Map.delete(params, "slug")
end
# Validate url_slug before saving (for translations)
# For post slug conflicts, we auto-clear and show a notice instead of blocking
case validate_url_slug_for_save(socket, params) do
{:ok, validated_params} ->
do_perform_save(socket, validated_params)
{:ok, validated_params, notice} ->
socket = Phoenix.LiveView.put_flash(socket, :info, notice)
do_perform_save(socket, validated_params)
{:error, reason} ->
error_message = url_slug_error_message(reason)
{:noreply, Phoenix.LiveView.put_flash(socket, :error, error_message)}
end
end
defp validate_url_slug_for_save(socket, params) do
url_slug = Map.get(params, "url_slug", "")
if url_slug != "" do
group_slug = socket.assigns.group_slug
language = editor_language(socket.assigns)
post_slug = socket.assigns.post.slug || socket.assigns.post[:uuid]
case Publishing.validate_url_slug(group_slug, url_slug, language, post_slug) do
{:ok, _} ->
{:ok, params}
{:error, :conflicts_with_post_slug} ->
# Auto-clear the url_slug from ALL translations of this post
cleared_params = Map.put(params, "url_slug", "")
cleared_languages = Publishing.clear_url_slug_from_post(group_slug, post_slug, url_slug)
notice =
if length(cleared_languages) > 1 do
gettext(
"Custom URL slug '%{slug}' was cleared from %{count} translations because it conflicts with another post's post slug",
slug: url_slug,
count: length(cleared_languages)
)
else
gettext(
"Custom URL slug '%{slug}' for %{language} was cleared because it conflicts with another post's post slug",
slug: url_slug,
language: language
)
end
{:ok, cleared_params, notice}
{:error, :slug_already_exists} ->
# Auto-clear the url_slug from ALL translations of this post
cleared_params = Map.put(params, "url_slug", "")
cleared_languages = Publishing.clear_url_slug_from_post(group_slug, post_slug, url_slug)
notice =
if length(cleared_languages) > 1 do
gettext(
"Custom URL slug '%{slug}' was cleared from %{count} translations because it's already in use by another post",
slug: url_slug,
count: length(cleared_languages)
)
else
gettext(
"Custom URL slug '%{slug}' for %{language} was cleared because it's already in use by another post",
slug: url_slug,
language: language
)
end
{:ok, cleared_params, notice}
{:error, reason} ->
{:error, reason}
end
else
{:ok, params}
end
end
defp url_slug_error_message(:invalid_format),
do: gettext("URL slug must be lowercase letters, numbers, and hyphens only")
defp url_slug_error_message(:reserved_language_code),
do: gettext("URL slug cannot be a language code")
defp url_slug_error_message(:reserved_route_word),
do: gettext("URL slug cannot be a reserved word (admin, api, assets, etc.)")
defp do_perform_save(socket, params) do
is_new_post = Map.get(socket.assigns, :is_new_post, false)
is_new_translation = Map.get(socket.assigns, :is_new_translation, false)
# Check if translation was created in background
{socket, is_new_translation} =
if is_new_translation do
check_background_translation_creation(socket)
else
{socket, false}
end
cond do
is_new_post ->
create_new_post(socket, params)
is_new_translation ->
create_new_translation(socket, params)
true ->
update_existing_post(socket, params)
end
end
defp check_background_translation_creation(socket) do
target_language = socket.assigns.current_language
# Check if content was created in DB for this language.
# Verify the returned post's language matches — resolve_content falls back
# to the primary language when the requested language doesn't exist yet,
# which would trick us into thinking the translation was already created.
case re_read_post(socket, target_language, socket.assigns.post[:version]) do
{:ok, real_post}
when real_post.language == target_language and
real_post.content != nil and real_post.content != "" ->
socket =
socket
|> Phoenix.Component.assign(:post, real_post)
|> Phoenix.Component.assign(:is_new_translation, false)
{socket, false}
_ ->
{socket, true}
end
end
# ============================================================================
# Create Operations
# ============================================================================
defp create_new_post(socket, params) do
scope = socket.assigns[:phoenix_kit_current_scope]
create_opts =
if socket.assigns.group_mode == "slug" do
%{
title: Map.get(params, "title"),
slug: Map.get(params, "slug")
}
else
%{}
end
|> Map.put(:scope, scope)
case Publishing.create_post(socket.assigns.group_slug, create_opts) do
{:ok, new_post} ->
uuid = new_post[:uuid]
result =
case Publishing.update_post(socket.assigns.group_slug, new_post, params, %{scope: scope}) do
{:ok, updated_post} ->
# Preserve UUID from create_post (update_post may not include it)
{:ok, if(uuid, do: Map.put(updated_post, :uuid, uuid), else: updated_post)}
error ->
error
end
handle_post_update_result(socket, result, gettext("Post created and saved"), %{
is_new_post: false
})
{:error, error} ->
handle_post_creation_error(socket, error, gettext("Failed to create post"))
end
end
defp create_new_translation(socket, params) do
scope = socket.assigns[:phoenix_kit_current_scope]
current_version = socket.assigns[:current_version]
case Publishing.add_language_to_post(
socket.assigns.group_slug,
socket.assigns.post.uuid,
socket.assigns.current_language,
current_version
) do
{:ok, new_post} ->
case Publishing.update_post(socket.assigns.group_slug, new_post, params, %{
scope: scope,
is_primary_language: false
}) do
{:ok, _updated_post} = result ->
handle_post_update_result(
socket,
result,
gettext("Translation created and saved"),
%{is_new_translation: false}
)
error ->
handle_post_update_result(
socket,
error,
gettext("Translation created and saved"),
%{is_new_translation: false}
)
end
{:error, _reason} ->
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext("Failed to create translation")
)}
end
end
# ============================================================================
# Update Operations
# ============================================================================
defp update_existing_post(socket, params) do
scope = socket.assigns[:phoenix_kit_current_scope]
post = socket.assigns.post
language = socket.assigns.current_language
# Check if we need to create a new version
should_create_version =
Publishing.should_create_new_version?(post, params, language)
if should_create_version do
create_new_version_from_edit(socket, params, scope)
else
update_post_in_place(socket, params, scope)
end
end
defp create_new_version_from_edit(socket, params, scope) do
group_slug = socket.assigns.group_slug
post = socket.assigns.post
case Publishing.create_new_version(group_slug, post, params, %{scope: scope}) do
{:ok, new_version_post} ->
invalidate_post_cache(group_slug, new_version_post)
socket =
socket
|> Phoenix.Component.assign(:post, new_version_post)
|> Phoenix.Component.assign(:content, new_version_post.content)
|> Phoenix.Component.assign(:current_version, new_version_post.version)
|> Phoenix.Component.assign(:available_versions, new_version_post.available_versions)
|> Phoenix.Component.assign(:version_statuses, new_version_post.version_statuses)
|> Phoenix.Component.assign(
:version_dates,
Map.get(new_version_post, :version_dates, %{})
)
|> Phoenix.Component.assign(:editing_published_version, false)
|> Phoenix.Component.assign(:has_pending_changes, false)
|> Phoenix.LiveView.push_event("changes-status", %{has_changes: false})
|> Phoenix.LiveView.put_flash(
:info,
gettext("Created new version %{version} (draft)",
version: new_version_post.version
)
)
|> Phoenix.LiveView.push_patch(
to:
Helpers.build_edit_url(group_slug, new_version_post,
version: new_version_post.version
)
)
{:noreply, socket}
{:error, reason} ->
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext("Failed to create new version: %{reason}", reason: inspect(reason))
)}
end
end
defp update_post_in_place(socket, params, scope) do
group_slug = socket.assigns.group_slug
# Ensure the post's language matches the current editing language,
# not the stale language from when the post was initially loaded
post = %{socket.assigns.post | language: socket.assigns.current_language}
current_version = socket.assigns[:current_version]
# Use saved_status (stored status) not post.metadata.status (form-updated status)
saved_status = socket.assigns[:saved_status] || Map.get(post.metadata, :status, "draft")
is_primary_language = socket.assigns[:is_primary_language] == true
# For translations: if primary is not published, force status to match primary
params = enforce_translation_status(params, socket, is_primary_language)
new_status = Map.get(params, "status")
# Check if this is a status change TO published for a versioned post
is_publishing =
should_publish_version?(
is_primary_language,
new_status,
saved_status,
current_version,
post
)
case Publishing.update_post(group_slug, post, params, %{
scope: scope,
is_primary_language: is_primary_language
}) do
{:ok, updated_post} ->
handle_successful_update(
socket,
updated_post,
is_publishing,
post,
current_version
)
{:error, error} ->
handle_post_in_place_error(socket, error)
end
end
defp enforce_translation_status(params, _socket, true = _is_primary), do: params
defp enforce_translation_status(params, socket, false = _is_primary) do
primary_status = socket.assigns.form["status"]
if primary_status == "published" do
# When primary is published, auto-publish translations so they
# appear on the public site immediately after saving
Map.put(params, "status", "published")
else
Map.put(params, "status", primary_status)
end
end
defp should_publish_version?(is_primary, new_status, current_status, current_version, _post) do
is_primary and
new_status == "published" and
current_status != "published" and
current_version != nil
end
# ============================================================================
# Success/Error Handlers
# ============================================================================
defp handle_successful_update(
socket,
updated_post,
false = _is_publishing,
_post,
_version
) do
handle_post_save_success(socket, updated_post)
end
defp handle_successful_update(
socket,
updated_post,
true = _is_publishing,
post,
current_version
) do
group_slug = socket.assigns.group_slug
# Use user UUID so all tabs for the same user recognize their own publishes
user_uuid =
get_in(socket.assigns, [:phoenix_kit_current_scope, Access.key(:user), Access.key(:uuid)])
case Publishing.publish_version(group_slug, post.uuid, current_version, source_id: user_uuid) do
:ok ->
handle_post_save_success(socket, updated_post)
{:error, reason} ->
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:warning,
gettext("Post saved but failed to archive other versions: %{reason}",
reason: inspect(reason)
)
)}
end
end
defp handle_post_save_success(socket, post) do
group_slug = socket.assigns.group_slug
invalidate_post_cache(group_slug, post)
# Broadcast save to other tabs/users
if socket.assigns[:form_key] do
Logger.debug(
"BROADCASTING editor_saved from update_existing_post: " <>
"form_key=#{inspect(socket.assigns.form_key)}, source=#{inspect(socket.id)}"
)
PublishingPubSub.broadcast_editor_saved(socket.assigns.form_key, socket.id)
end
flash_message =
if socket.assigns.is_autosaving,
do: nil,
else: gettext("Post saved")
# Re-read post to get fresh cross-version statuses
current_version = socket.assigns[:current_version]
current_language = socket.assigns[:current_language]
refreshed_post =
case re_read_post(socket, current_language, current_version) do
{:ok, fresh_post} ->
fresh_post
{:error, reason} ->
Logger.warning(
"Failed to re-read post after save: #{inspect(reason)}, post: #{post[:slug] || post[:uuid]}"
)
current_language_statuses = Map.get(post, :language_statuses, %{})
new_status = Map.get(post.metadata, :status, "draft")
updated_statuses = Map.put(current_language_statuses, current_language, new_status)
Map.put(post, :language_statuses, updated_statuses)
end
form = Forms.post_form_with_primary_status(group_slug, refreshed_post, current_version)
is_published = form["status"] == "published"
# Update saved_status to reflect the newly saved status
new_saved_status = form["status"]
socket =
socket
|> Phoenix.Component.assign(:post, refreshed_post)
|> Forms.assign_form_with_tracking(form)
|> Phoenix.Component.assign(:content, refreshed_post.content)
|> Phoenix.Component.assign(:has_pending_changes, false)
|> Phoenix.Component.assign(:editing_published_version, is_published)
|> Phoenix.Component.assign(:saved_status, new_saved_status)
|> Phoenix.Component.assign(:language_statuses, refreshed_post.language_statuses)
|> Phoenix.Component.assign(:version_statuses, refreshed_post.version_statuses)
|> Phoenix.Component.assign(:version_dates, Map.get(refreshed_post, :version_dates, %{}))
|> Phoenix.LiveView.push_event("changes-status", %{has_changes: false})
{:noreply,
if(flash_message, do: Phoenix.LiveView.put_flash(socket, :info, flash_message), else: socket)}
end
defp handle_post_in_place_error(socket, :invalid_format) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"Invalid slug format. Please use only lowercase letters, numbers, and hyphens (e.g. my-post-title)"
)
)}
end
defp handle_post_in_place_error(socket, :reserved_language_code) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"This slug is reserved because it's a language code (like 'en', 'es', 'fr'). Please choose a different slug to avoid routing conflicts."
)
)}
end
defp handle_post_in_place_error(socket, :invalid_slug) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"Invalid slug format. Please use only lowercase letters, numbers, and hyphens (e.g. my-post-title)"
)
)}
end
defp handle_post_in_place_error(socket, :slug_already_exists) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext("A post with that slug already exists")
)}
end
defp handle_post_in_place_error(socket, reason) do
post_id = socket.assigns[:post] && socket.assigns.post[:uuid]
Logger.warning("[Publishing.Editor] Save failed for post #{post_id}: #{inspect(reason)}")
{:noreply, Phoenix.LiveView.put_flash(socket, :error, gettext("Failed to save post"))}
end
defp handle_post_update_result(socket, update_result, success_message, extra_assigns) do
case update_result do
{:ok, updated_post} ->
invalidate_post_cache(socket.assigns.group_slug, updated_post)
if socket.assigns[:form_key] do
Logger.debug(
"BROADCASTING editor_saved: " <>
"form_key=#{inspect(socket.assigns.form_key)}, source=#{inspect(socket.id)}"
)
PublishingPubSub.broadcast_editor_saved(socket.assigns.form_key, socket.id)
end
flash_message =
if socket.assigns.is_autosaving,
do: nil,
else: success_message
alias PhoenixKit.Modules.Publishing.Web.Editor.Forms
form = Forms.post_form(updated_post)
language = updated_post.language || socket.assigns[:current_language]
public_url = Helpers.build_public_url(updated_post, language)
socket =
socket
|> Phoenix.Component.assign(:post, updated_post)
|> Phoenix.Component.assign(:public_url, public_url)
|> Forms.assign_form_with_tracking(form)
|> Phoenix.Component.assign(:content, updated_post.content)
|> Phoenix.Component.assign(:available_languages, updated_post.available_languages)
|> Phoenix.Component.assign(:has_pending_changes, false)
|> Phoenix.Component.assign(extra_assigns)
|> Phoenix.LiveView.push_event("changes-status", %{has_changes: false})
|> Phoenix.LiveView.push_patch(
to:
Helpers.build_edit_url(socket.assigns.group_slug, updated_post,
lang: updated_post.language,
version: updated_post[:version]
)
)
{:noreply,
if(flash_message,
do: Phoenix.LiveView.put_flash(socket, :info, flash_message),
else: socket
)}
{:error, error} ->
handle_post_update_error(socket, error)
end
end
defp handle_post_update_error(socket, :invalid_format) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"Invalid slug format. Please use only lowercase letters, numbers, and hyphens (e.g. my-post-title)"
)
)}
end
defp handle_post_update_error(socket, :reserved_language_code) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"This slug is reserved because it's a language code (like 'en', 'es', 'fr'). Please choose a different slug to avoid routing conflicts."
)
)}
end
defp handle_post_update_error(socket, :invalid_slug) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"Invalid slug format. Please use only lowercase letters, numbers, and hyphens (e.g. my-post-title)"
)
)}
end
defp handle_post_update_error(socket, :slug_already_exists) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext("A post with that slug already exists")
)}
end
defp handle_post_update_error(socket, :title_required) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext("Title is required.")
)}
end
defp handle_post_update_error(socket, reason) do
post_id = socket.assigns[:post] && socket.assigns.post[:uuid]
Logger.warning("[Publishing.Editor] Update failed for post #{post_id}: #{inspect(reason)}")
{:noreply, Phoenix.LiveView.put_flash(socket, :error, gettext("Failed to save post"))}
end
defp slug_constraint_error?(changeset) do
Keyword.has_key?(changeset.errors, :slug) or
Enum.any?(changeset.errors, fn
{:group_uuid, {_, opts}} ->
Keyword.get(opts, :constraint_name) == "idx_publishing_posts_group_slug"
_ ->
false
end)
end
defp handle_post_creation_error(socket, :invalid_slug, _fallback_message) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"Invalid slug format. Please use only lowercase letters, numbers, and hyphens (e.g. my-post-title)"
)
)}
end
defp handle_post_creation_error(socket, :slug_already_exists, _fallback_message) do
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext(
"A post with that slug already exists. Please choose a different title or edit the slug manually."
)
)}
end
defp handle_post_creation_error(socket, %Ecto.Changeset{} = changeset, fallback_message) do
if slug_constraint_error?(changeset) do
handle_post_creation_error(socket, :slug_already_exists, fallback_message)
else
group = socket.assigns[:group_slug]
Logger.warning(
"[Publishing.Editor] Post creation failed in #{group}: #{inspect(changeset.errors)}"
)
{:noreply, Phoenix.LiveView.put_flash(socket, :error, fallback_message)}
end
end
defp handle_post_creation_error(socket, reason, fallback_message) do
group = socket.assigns[:group_slug]
Logger.warning("[Publishing.Editor] Post creation failed in #{group}: #{inspect(reason)}")
{:noreply, Phoenix.LiveView.put_flash(socket, :error, fallback_message)}
end
# ============================================================================
# Helpers
# ============================================================================
defp re_read_post(socket, language \\ nil, version \\ nil) do
post = socket.assigns.post
Publishing.read_post_by_uuid(post.uuid, language, version)
end
defp invalidate_post_cache(group_slug, post) do
identifier = post.slug
Renderer.invalidate_cache(group_slug, identifier, post.language)
end
defp editor_language(assigns), do: Helpers.editor_language(assigns)
# ============================================================================
# Reload Operations
# ============================================================================
@doc """
Reload content after AI translation completes for the current language.
"""
def reload_translated_content(socket, flash_msg, flash_level) do
group_slug = socket.assigns.group_slug
current_language = socket.assigns[:current_language]
case re_read_post(socket, current_language) do
{:ok, updated_post} ->
current_version = socket.assigns[:current_version]
form = Forms.post_form_with_primary_status(group_slug, updated_post, current_version)
socket
|> Phoenix.Component.assign(:post, %{updated_post | group: group_slug})
|> Forms.assign_form_with_tracking(form)
|> Phoenix.Component.assign(:content, updated_post.content)
|> Phoenix.Component.assign(:available_languages, updated_post.available_languages)
|> Phoenix.Component.assign(:has_pending_changes, false)
|> Phoenix.LiveView.push_event("changes-status", %{has_changes: false})
|> Phoenix.LiveView.push_event("set-content", %{content: updated_post.content})
|> Phoenix.LiveView.put_flash(flash_level, flash_msg)
{:error, _reason} ->
Phoenix.LiveView.put_flash(socket, flash_level, flash_msg)
end
end
@doc """
Refresh available_languages and language_statuses (for language switcher updates).
"""
def refresh_available_languages(socket) do
case re_read_post(socket) do
{:ok, updated_post} ->
socket
|> Phoenix.Component.assign(:available_languages, updated_post.available_languages)
|> Phoenix.Component.assign(
:post,
socket.assigns.post
|> Map.put(:available_languages, updated_post.available_languages)
|> Map.put(:language_statuses, updated_post.language_statuses)
)
{:error, _reason} ->
socket
end
end
@doc """
Reload post when another tab/user saves (last-save-wins).
"""
def reload_post(socket) do
group_slug = socket.assigns.group_slug
current_language = socket.assigns[:current_language]
current_version = socket.assigns[:current_version]
case re_read_post(socket, current_language) do
{:ok, updated_post} ->
form = Forms.post_form_with_primary_status(group_slug, updated_post, current_version)
socket
|> Phoenix.Component.assign(:post, %{updated_post | group: group_slug})
|> Forms.assign_form_with_tracking(form)
|> Phoenix.Component.assign(:content, updated_post.content)
|> Phoenix.Component.assign(:available_languages, updated_post.available_languages)
|> Phoenix.Component.assign(:has_pending_changes, false)
|> Phoenix.LiveView.push_event("changes-status", %{has_changes: false})
|> Phoenix.LiveView.push_event("set-content", %{content: updated_post.content})
|> Phoenix.LiveView.put_flash(:info, gettext("Post updated by another user"))
{:error, _reason} ->
socket
|> Phoenix.LiveView.put_flash(
:warning,
gettext("Could not reload post - it may have been moved or deleted")
)
end
end
@doc """
Regenerates the listing cache for a group.
"""
def regenerate_listing_cache(group_slug) do
ListingCache.regenerate(group_slug)
end
end