Packages
phoenix_kit
1.4.7
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/phoenix_kit_web/live/modules/publishing/editor.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={@page_title}
current_path={
PhoenixKit.Utils.Routes.path("/admin/publishing/#{@type_slug}/edit", locale: @current_locale)
}
project_title={@project_title}
current_locale={@current_locale}
>
<script>
(function() {
// Track unsaved changes state
window.editorUnsavedChanges = false;
// Browser exit protection
window.addEventListener('beforeunload', function(e) {
if (window.editorUnsavedChanges) {
e.preventDefault();
e.returnValue = '';
return '';
}
});
// Intercept navigation links
document.addEventListener('click', function(e) {
if (window.editorUnsavedChanges) {
const link = e.target.closest('a[href], a[data-phx-link]');
if (link && !link.hasAttribute('data-confirm')) {
const href = link.getAttribute('href') || link.getAttribute('data-phx-link');
// Don't block external links or hash links
if (href && !href.startsWith('http') && !href.startsWith('#')) {
e.preventDefault();
e.stopPropagation();
document.getElementById('confirm-cancel-btn').click();
}
}
}
}, true);
// Listen for changes status from LiveView
window.addEventListener('phx:changes-status', function(e) {
window.editorUnsavedChanges = e.detail.has_changes;
});
// Listen for confirm navigation event
window.addEventListener('phx:confirm-navigation', function(e) {
if (confirm('You have unsaved changes. Are you sure you want to leave?')) {
document.getElementById('confirm-cancel-btn').click();
}
});
})();
</script>
<%!-- Hidden confirmation button for JavaScript --%>
<button
id="confirm-cancel-btn"
type="button"
phx-click="cancel"
class="hidden"
aria-hidden="true"
>
</button>
<div class="container mx-auto px-4 py-6 space-y-6">
<div class="flex items-center justify-between">
<button type="button" class="btn btn-outline btn-sm shadow-none" phx-click="back_to_list">
<.icon name="hero-arrow-left" class="w-4 h-4 mr-1" /> {gettext("Back to %{type}",
type: @type_name
)}
</button>
<div class="flex items-center gap-3">
<%!-- Language Switcher --%>
<%= if length(@all_enabled_languages) > 1 do %>
<div class="flex items-center gap-2" id="language-switcher">
<span class="text-sm text-base-content/70">{gettext("Language")}:</span>
<div class="join">
<%= for lang_code <- @all_enabled_languages do %>
<% lang_info = PhoenixKitWeb.Live.Modules.Publishing.get_language_info(lang_code)
flag = if lang_info, do: lang_info.flag, else: ""
is_current = @current_language == lang_code
file_exists = lang_code in @available_languages %>
<button
type="button"
class={[
"join-item btn btn-sm shadow-none",
cond do
is_current -> "btn-primary"
file_exists -> "btn-outline"
true -> "btn-success bg-green-50 hover:bg-green-100"
end
]}
phx-click="switch_language"
phx-value-language={lang_code}
title={
cond do
is_current && lang_info -> "Editing #{lang_info.name}"
file_exists && lang_info -> "Switch to #{lang_info.name}"
lang_info -> "Create #{lang_info.name} translation"
true -> lang_code
end
}
>
<span class="text-lg phx-click-loading:opacity-50">{flag}</span>
<span class="phx-click-loading:hidden">{String.upcase(lang_code)}</span>
<span class="hidden phx-click-loading:inline-block loading loading-spinner loading-xs">
</span>
</button>
<% end %>
</div>
</div>
<% end %>
<button
type="button"
class="btn btn-outline btn-sm shadow-none"
phx-click="preview"
>
<.icon name="hero-eye" class="w-4 h-4 mr-1" /> {gettext("Preview")}
</button>
</div>
</div>
<div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body space-y-6">
<div class="flex flex-col lg:flex-row gap-6">
<div class="lg:w-80 space-y-4">
<.form for={@form} id="publishing-meta" phx-change="update_meta" phx-submit="noop">
<div class="space-y-4">
<div>
<label class="label">
<span class="label-text text-sm font-semibold text-base-content">
{gettext("Title")}
</span>
</label>
<input
type="text"
name="title"
value={@form["title"]}
class="input input-bordered w-full"
placeholder={gettext("Entry title")}
/>
</div>
<div>
<label class="label">
<span class="label-text text-sm font-semibold text-base-content">
{gettext("Status")}
</span>
</label>
<select
class="select select-bordered w-full"
name="status"
>
<option value="draft" selected={@form["status"] == "draft"}>
{gettext("Draft")}
</option>
<option value="published" selected={@form["status"] == "published"}>
{gettext("Published")}
</option>
<option value="archived" selected={@form["status"] == "archived"}>
{gettext("Archived")}
</option>
</select>
</div>
<div>
<label class="label">
<span class="label-text text-sm font-semibold text-base-content">
{gettext("Publication Date & Time (UTC)")}
</span>
</label>
<input
type="datetime-local"
name="published_at"
value={datetime_local_value(@form["published_at"])}
class="input input-bordered w-full"
/>
<p class="text-xs text-base-content/60 mt-1">
{gettext(
"Updating the publication time moves the file to the matching folder."
)}
</p>
</div>
</div>
</.form>
</div>
<div class="flex-1 space-y-4">
<div class="flex flex-wrap items-center justify-between gap-3">
<h2 class="text-xl font-semibold text-base-content">{gettext("Content")}</h2>
<div class="flex items-center gap-2">
<%= if @has_pending_changes do %>
<span class="badge badge-warning badge-sm">{gettext("Unsaved changes")}</span>
<% end %>
<button
type="button"
class={[
"btn btn-sm inline-flex items-center justify-center gap-2 px-3",
if(@has_pending_changes, do: "btn-primary", else: "btn-disabled")
]}
phx-click="save"
disabled={!@has_pending_changes}
>
<.icon name="hero-check" class="w-4 h-4" />
<span class="whitespace-nowrap">{gettext("Save Entry")}</span>
</button>
</div>
</div>
<.form for={%{}} id="publishing-content" phx-change="update_content" phx-submit="noop">
<textarea
name="content"
class="textarea textarea-bordered w-full h-[480px] font-mono text-sm leading-6"
>{@content}</textarea>
</.form>
</div>
</div>
</div>
</div>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>