Packages
phoenix_kit
1.7.82
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/pages/web/editor.ex
defmodule PhoenixKit.Modules.Pages.Web.Editor do
@moduledoc """
Full-screen editor for Pages files.
Provides a dedicated page for editing markdown content with metadata.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.Pages
alias PhoenixKit.Modules.Pages.FileOperations
alias PhoenixKit.Modules.Pages.HtmlMetadata
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.Routes
def mount(_params, _session, socket) do
# Set locale
locale = socket.assigns[:current_locale] || "en"
# Check if module is enabled
if Pages.enabled?() do
socket =
socket
|> assign(:page_title, "Edit Page")
|> assign(:file_path, nil)
|> assign(:file_content, "")
|> assign(:original_content, "")
|> assign(:has_changes, false)
|> assign(:project_title, PhoenixKit.Settings.get_project_title())
|> assign(:current_locale, locale)
{:ok, socket}
else
socket =
socket
|> put_flash(:error, "Pages module is not enabled")
|> redirect(to: Routes.path("/admin/modules"))
{:ok, socket}
end
end
def handle_params(%{"path" => path}, _uri, socket) do
case FileOperations.read_file(path) do
{:ok, content} ->
# Extract current status from metadata
current_status =
case HtmlMetadata.parse(content) do
{:ok, metadata, _stripped} -> metadata.status
{:error, :no_metadata} -> "draft"
end
socket =
socket
|> assign(:file_path, path)
|> assign(:file_content, content)
|> assign(:original_content, content)
|> assign(:has_changes, false)
|> assign(:current_status, current_status)
|> assign(:page_title, "Edit: #{Path.basename(path)}")
# Notify JavaScript hook about initial changes status (no changes)
{:noreply, push_event(socket, "changes-status", %{has_changes: false})}
{:error, _reason} ->
socket =
socket
|> put_flash(:error, "File not found: #{path}")
|> redirect(to: Routes.path("/admin/pages"))
{:noreply, socket}
end
end
def handle_params(_params, _uri, socket) do
# No path provided, redirect to file list
socket =
socket
|> put_flash(:error, "No file path provided")
|> redirect(to: Routes.path("/admin/pages"))
{:noreply, socket}
end
## Event Handlers
def handle_event("update_content", %{"value" => content}, socket) do
has_changes = content != socket.assigns.original_content
socket =
socket
|> assign(:file_content, content)
|> assign(:has_changes, has_changes)
# Notify JavaScript hook about changes status
{:noreply, push_event(socket, "changes-status", %{has_changes: has_changes})}
end
def handle_event("save", _params, socket) do
file_path = socket.assigns.file_path
content = socket.assigns.file_content
# Update metadata's updated_at timestamp
updated_content = update_metadata_timestamp(content)
try do
case FileOperations.write_file(file_path, updated_content) do
:ok ->
socket =
socket
|> assign(:original_content, updated_content)
|> assign(:file_content, updated_content)
|> assign(:has_changes, false)
|> put_flash(:info, "File saved: #{Path.basename(file_path)}")
# Notify JavaScript hook that changes have been saved
{:noreply, push_event(socket, "changes-status", %{has_changes: false})}
{:error, _reason} ->
socket = put_flash(socket, :error, "Failed to save file")
{:noreply, socket}
end
rescue
e ->
require Logger
Logger.error("Page editor save failed: #{Exception.message(e)}")
{:noreply, put_flash(socket, :error, "Something went wrong. Please try again.")}
end
end
def handle_event("attempt_cancel", _params, socket) do
if socket.assigns.has_changes do
# Show browser's native confirm dialog via JavaScript
{:noreply,
push_event(socket, "confirm-navigation", %{
message: "You have unsaved changes. Are you sure you want to leave without saving?",
target: "/admin/pages"
})}
else
# No changes, navigate immediately
{:noreply, redirect(socket, to: Routes.path("/admin/pages"))}
end
end
def handle_event("cancel", _params, socket) do
# User confirmed via JavaScript, navigate away
{:noreply, redirect(socket, to: Routes.path("/admin/pages"))}
end
def handle_event("change_status", %{"status" => new_status}, socket) do
content = socket.assigns.file_content
file_path = socket.assigns.file_path
# Update metadata with new status and timestamp
updated_content = update_metadata_status(content, new_status)
case FileOperations.write_file(file_path, updated_content) do
:ok ->
socket =
socket
|> assign(:file_content, updated_content)
|> assign(:original_content, updated_content)
|> assign(:current_status, new_status)
|> assign(:has_changes, false)
|> put_flash(:info, "Status changed to: #{new_status}")
{:noreply, socket}
{:error, _reason} ->
socket = put_flash(socket, :error, "Failed to update status")
{:noreply, socket}
end
end
## Private Helpers
defp update_metadata_timestamp(content) do
case HtmlMetadata.parse(content) do
{:ok, metadata, _stripped_content} ->
# Update the updated_at timestamp
updated_metadata = Map.put(metadata, :updated_at, UtilsDate.utc_now())
HtmlMetadata.update_metadata(content, updated_metadata)
{:error, :no_metadata} ->
# No metadata, return content as-is
content
end
end
defp update_metadata_status(content, new_status) do
case HtmlMetadata.parse(content) do
{:ok, metadata, _stripped_content} ->
# Update both status and updated_at timestamp
updated_metadata =
metadata
|> Map.put(:status, new_status)
|> Map.put(:updated_at, UtilsDate.utc_now())
HtmlMetadata.update_metadata(content, updated_metadata)
{:error, :no_metadata} ->
# No metadata exists, create new metadata with the status
metadata =
HtmlMetadata.default_metadata()
|> Map.put(:status, new_status)
|> Map.put(:updated_at, UtilsDate.utc_now())
# Prepend metadata to content
HtmlMetadata.serialize(metadata) <> "\n\n" <> content
end
end
end