Packages
phoenix_kit
1.7.37
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/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.Storage
alias PhoenixKit.Modules.Publishing.Web.Editor.Forms
alias PhoenixKit.Utils.Routes
require Logger
# ============================================================================
# Save Orchestration
# ============================================================================
@doc """
Performs save operation with validation and routing.
Returns {:noreply, socket}.
"""
def perform_save(socket) do
params =
socket.assigns.form
|> Map.take(["status", "published_at", "slug", "featured_image_id", "url_slug"])
|> Map.put("content", socket.assigns.content)
params =
case {socket.assigns.blog_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 directory 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
blog_slug = socket.assigns.blog_slug
language = editor_language(socket.assigns)
post_slug = socket.assigns.post.slug
case Storage.validate_url_slug(blog_slug, url_slug, language, post_slug) do
{:ok, _} ->
{:ok, params}
{:error, :conflicts_with_directory_slug} ->
# Auto-clear the url_slug from ALL translations of this post
cleared_params = Map.put(params, "url_slug", "")
cleared_languages = Storage.clear_url_slug_from_post(blog_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 directory name",
slug: url_slug,
count: length(cleared_languages)
)
else
gettext(
"Custom URL slug '%{slug}' for %{language} was cleared because it conflicts with another post's directory name",
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 = Storage.clear_url_slug_from_post(blog_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 file 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
post = socket.assigns.post
expected_path = post[:path] && Storage.absolute_path(post.path)
file_exists = expected_path && File.exists?(expected_path)
if file_exists do
case Publishing.read_post(socket.assigns.blog_slug, post.path) do
{:ok, real_post} ->
socket =
socket
|> Phoenix.Component.assign(:post, real_post)
|> Phoenix.Component.assign(:is_new_translation, false)
{socket, false}
{:error, _} ->
{socket, true}
end
else
{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.blog_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.blog_slug, create_opts) do
{:ok, new_post} ->
case Publishing.update_post(socket.assigns.blog_slug, new_post, params, %{scope: scope}) do
{:ok, _updated_post} = result ->
handle_post_update_result(socket, result, gettext("Post created and saved"), %{
is_new_post: false
})
error ->
handle_post_update_result(socket, error, gettext("Post created and saved"), %{
is_new_post: false
})
end
{: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]
original_identifier =
case socket.assigns.blog_mode do
"slug" ->
socket.assigns.post.slug ||
Map.get(socket.assigns, :original_post_path, socket.assigns.post.path)
_ ->
Map.get(socket.assigns, :original_post_path, socket.assigns.post.path)
end
current_version = socket.assigns[:current_version]
case Publishing.add_language_to_post(
socket.assigns.blog_slug,
original_identifier,
socket.assigns.current_language,
current_version
) do
{:ok, new_post} ->
case Publishing.update_post(socket.assigns.blog_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,
original_post_path: nil
}
)
error ->
handle_post_update_result(
socket,
error,
gettext("Translation created and saved"),
%{
is_new_translation: false,
original_post_path: nil
}
)
end
{:error, _reason} ->
{:noreply,
Phoenix.LiveView.put_flash(
socket,
:error,
gettext("Failed to create translation file")
)}
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
old_path = post.path
# Check if we need to create a new version
should_create_version =
not Map.get(post, :is_legacy_structure, false) and
Storage.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, old_path)
end
end
defp create_new_version_from_edit(socket, params, scope) do
blog_slug = socket.assigns.blog_slug
post = socket.assigns.post
case Publishing.create_new_version(blog_slug, post, params, %{scope: scope}) do
{:ok, new_version_post} ->
invalidate_post_cache(blog_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:
Routes.path(
"/admin/publishing/#{blog_slug}/edit?path=#{URI.encode(new_version_post.path)}"
)
)
{: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, old_path) do
blog_slug = socket.assigns.blog_slug
post = socket.assigns.post
current_version = socket.assigns[:current_version]
# Use saved_status (on-disk 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(blog_slug, post, params, %{
scope: scope,
is_primary_language: is_primary_language
}) do
{:ok, updated_post} ->
handle_successful_update(
socket,
updated_post,
old_path,
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
params
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 and
not Map.get(post, :is_legacy_structure, false)
end
# ============================================================================
# Success/Error Handlers
# ============================================================================
defp handle_successful_update(
socket,
updated_post,
old_path,
false = _is_publishing,
_post,
_version
) do
handle_post_save_success(socket, updated_post, old_path)
end
defp handle_successful_update(
socket,
updated_post,
old_path,
true = _is_publishing,
post,
current_version
) do
blog_slug = socket.assigns.blog_slug
post_identifier =
case post.mode do
:timestamp -> extract_timestamp_identifier(post.path)
_ -> post.slug
end
# Use user ID so all tabs for the same user recognize their own publishes
user_id =
get_in(socket.assigns, [:phoenix_kit_current_scope, Access.key(:user), Access.key(:id)])
case Publishing.publish_version(blog_slug, post_identifier, current_version,
source_id: user_id
) do
:ok ->
handle_post_save_success(socket, updated_post, old_path)
{: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, old_path) do
blog_slug = socket.assigns.blog_slug
invalidate_post_cache(blog_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 from disk to get fresh cross-version statuses
current_version = socket.assigns[:current_version]
current_language = socket.assigns[:current_language]
refreshed_post =
case Publishing.read_post(blog_slug, post.path, current_language, current_version) do
{:ok, fresh_post} ->
fresh_post
{:error, reason} ->
# If re-read fails, log it and update language_statuses for current language
# to ensure the language switcher shows the correct status
Logger.warning(
"Failed to re-read post after save: #{inspect(reason)}, path: #{post.path}"
)
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(blog_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})
|> maybe_update_current_path(old_path, refreshed_post.path)
{: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
{: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.blog_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)
socket =
socket
|> Phoenix.Component.assign(:post, updated_post)
|> 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:
Routes.path(
"/admin/publishing/#{socket.assigns.blog_slug}/edit?path=#{URI.encode(updated_post.path)}"
)
)
{: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,
"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,
"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,
"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, "A post with that slug already exists")}
end
defp handle_post_update_error(socket, _reason) do
{:noreply, Phoenix.LiveView.put_flash(socket, :error, "Failed to save post")}
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")
)}
end
defp handle_post_creation_error(socket, _reason, fallback_message) do
{:noreply, Phoenix.LiveView.put_flash(socket, :error, fallback_message)}
end
# ============================================================================
# Helpers
# ============================================================================
defp invalidate_post_cache(blog_slug, post) do
identifier =
case Map.get(post, :mode) do
:slug -> post.slug
:timestamp -> extract_identifier_from_path(post.path)
_ -> post.slug || extract_identifier_from_path(post.path)
end
Renderer.invalidate_cache(blog_slug, identifier, post.language)
end
defp extract_identifier_from_path(path) when is_binary(path) do
path
|> String.split("/")
|> Enum.drop(-1)
|> Enum.drop(1)
|> Enum.join("/")
end
defp extract_timestamp_identifier(path) when is_binary(path) do
parts = String.split(path, "/", trim: true)
case parts do
[_blog, date, time, _version, _file] -> "#{date}/#{time}"
[_blog, date, time, _file] -> "#{date}/#{time}"
_ -> nil
end
end
defp extract_timestamp_identifier(_), do: nil
defp maybe_update_current_path(socket, old_path, new_path)
when new_path in [nil, ""] or new_path == old_path,
do: socket
defp maybe_update_current_path(socket, _old_path, new_path) do
encoded = URI.encode(new_path)
path =
Routes.path("/admin/publishing/#{socket.assigns.blog_slug}/edit?path=#{encoded}")
socket
|> Phoenix.Component.assign(:current_path, path)
|> Phoenix.LiveView.push_patch(to: path)
end
defp editor_language(assigns) do
assigns[:current_language] ||
assigns |> Map.get(:post, %{}) |> Map.get(:language) ||
hd(Storage.enabled_language_codes())
end
# ============================================================================
# Reload Operations
# ============================================================================
@doc """
Reload content after AI translation completes for the current language.
"""
def reload_translated_content(socket, flash_msg, flash_level) do
blog_slug = socket.assigns.blog_slug
post_path = socket.assigns.post.path
case Publishing.read_post(blog_slug, post_path) do
{:ok, updated_post} ->
current_version = socket.assigns[:current_version]
form = Forms.post_form_with_primary_status(blog_slug, updated_post, current_version)
socket
|> Phoenix.Component.assign(:post, %{updated_post | group: blog_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
blog_slug = socket.assigns.blog_slug
post_path = socket.assigns.post.path
case Publishing.read_post(blog_slug, post_path) 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 from disk when another tab/user saves (last-save-wins).
"""
def reload_post_from_disk(socket) do
blog_slug = socket.assigns.blog_slug
post_path = socket.assigns.post.path
current_version = socket.assigns[:current_version]
case Publishing.read_post(blog_slug, post_path) do
{:ok, updated_post} ->
form = Forms.post_form_with_primary_status(blog_slug, updated_post, current_version)
socket
|> Phoenix.Component.assign(:post, %{updated_post | group: blog_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 blog.
"""
def regenerate_listing_cache(blog_slug) do
ListingCache.regenerate(blog_slug)
end
end