chore: convert media to live component
This commit is contained in:
@@ -162,12 +162,6 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|> assign(:project_menu_open, false)
|
||||
|> assign(:sidebar_filters_by_view, %{})
|
||||
|> assign(:sidebar_filter_panels, %{})
|
||||
|> assign(:media_editor_drafts, %{})
|
||||
|> assign(:media_editor_quick_actions_open, %{})
|
||||
|> assign(:media_editor_post_pickers_open, %{})
|
||||
|> assign(:media_editor_post_picker_queries, %{})
|
||||
|> assign(:media_editor_save_states, %{})
|
||||
|> assign(:media_editor_translation_forms, %{})
|
||||
|> assign(:chat_editor_inputs, %{})
|
||||
|> assign(:chat_model_selectors_open, %{})
|
||||
|> assign(:chat_editor_requests, %{})
|
||||
@@ -331,116 +325,6 @@ defmodule BDS.Desktop.ShellLive do
|
||||
{:noreply, reload_shell(socket, workbench)}
|
||||
end
|
||||
|
||||
def handle_event("change_media_editor", %{"media_editor" => params}, socket) do
|
||||
{:noreply, MediaEditor.update(socket, params, &reload_shell/2)}
|
||||
end
|
||||
|
||||
def handle_event("save_media_editor", %{"id" => media_id}, socket) do
|
||||
{:noreply,
|
||||
MediaEditor.persist_socket(socket, media_id, &reload_shell/2, &append_output_entry/5)}
|
||||
end
|
||||
|
||||
def handle_event("toggle_media_editor_quick_actions", %{"id" => media_id}, socket) do
|
||||
{:noreply, MediaEditor.toggle_quick_actions(socket, media_id, &reload_shell/2)}
|
||||
end
|
||||
|
||||
def handle_event("replace_media_editor_file", %{"id" => media_id}, socket) do
|
||||
{:noreply,
|
||||
MediaEditor.replace_file(socket, media_id, &reload_shell/2, &append_output_entry/5)}
|
||||
end
|
||||
|
||||
def handle_event("detect_media_editor_language", %{"id" => media_id}, socket) do
|
||||
{:noreply,
|
||||
MediaEditor.detect_language(socket, media_id, &reload_shell/2, &append_output_entry/5)}
|
||||
end
|
||||
|
||||
def handle_event("toggle_media_post_picker", %{"id" => media_id}, socket) do
|
||||
{:noreply, MediaEditor.toggle_post_picker(socket, media_id, &reload_shell/2)}
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
"change_media_post_picker",
|
||||
%{"id" => media_id, "media_post_picker" => %{"query" => query}},
|
||||
socket
|
||||
) do
|
||||
{:noreply, MediaEditor.set_post_picker_query(socket, media_id, query, &reload_shell/2)}
|
||||
end
|
||||
|
||||
def handle_event("link_media_to_post", %{"id" => media_id, "post-id" => post_id}, socket) do
|
||||
{:noreply,
|
||||
MediaEditor.link_post(socket, media_id, post_id, &reload_shell/2, &append_output_entry/5)}
|
||||
end
|
||||
|
||||
def handle_event("unlink_media_from_post", %{"id" => media_id, "post-id" => post_id}, socket) do
|
||||
{:noreply,
|
||||
MediaEditor.unlink_post(socket, media_id, post_id, &reload_shell/2, &append_output_entry/5)}
|
||||
end
|
||||
|
||||
def handle_event("edit_media_translation", %{"id" => media_id, "language" => language}, socket) do
|
||||
{:noreply, MediaEditor.edit_translation(socket, media_id, language, &reload_shell/2)}
|
||||
end
|
||||
|
||||
def handle_event("change_media_translation", %{"media_translation" => params}, socket) do
|
||||
case socket.assigns.current_tab do
|
||||
%{type: :media, id: media_id} ->
|
||||
{:noreply, MediaEditor.update_translation(socket, media_id, params, &reload_shell/2)}
|
||||
|
||||
_other ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("save_media_translation", %{"id" => media_id}, socket) do
|
||||
{:noreply,
|
||||
MediaEditor.save_translation(socket, media_id, &reload_shell/2, &append_output_entry/5)}
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
"refresh_media_translation",
|
||||
%{"id" => media_id, "language" => language},
|
||||
socket
|
||||
) do
|
||||
{:noreply,
|
||||
MediaEditor.refresh_translation(
|
||||
socket,
|
||||
media_id,
|
||||
language,
|
||||
&reload_shell/2,
|
||||
&append_output_entry/5
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
"delete_media_translation",
|
||||
%{"id" => media_id, "language" => language},
|
||||
socket
|
||||
) do
|
||||
{:noreply,
|
||||
MediaEditor.delete_translation(
|
||||
socket,
|
||||
media_id,
|
||||
language,
|
||||
&reload_shell/2,
|
||||
&append_output_entry/5
|
||||
)}
|
||||
end
|
||||
|
||||
def handle_event("close_media_translation_editor", _params, socket) do
|
||||
case socket.assigns.current_tab do
|
||||
%{type: :media, id: media_id} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.delete(socket.assigns.media_editor_translation_forms, media_id)
|
||||
)
|
||||
|> reload_shell(socket.assigns.workbench)}
|
||||
|
||||
_other ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("settings_shell_command", %{"action" => action}, socket) do
|
||||
{:noreply, apply_shell_command(socket, action)}
|
||||
end
|
||||
@@ -693,11 +577,8 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|
||||
%{type: :media, id: media_id}
|
||||
when kind in ["ai_suggestions", "language_picker", "confirm_delete"] ->
|
||||
assign(
|
||||
socket,
|
||||
:media_editor_quick_actions_open,
|
||||
Map.put(socket.assigns.media_editor_quick_actions_open, media_id, false)
|
||||
)
|
||||
send_update(__MODULE__.MediaEditor, id: "media-editor-#{media_id}", action: :close_quick_actions)
|
||||
socket
|
||||
|
||||
_other ->
|
||||
socket
|
||||
@@ -868,7 +749,8 @@ defmodule BDS.Desktop.ShellLive do
|
||||
socket
|
||||
|
||||
{%{kind: :language_picker}, %{type: :media, id: media_id}} ->
|
||||
MediaEditor.translate(socket, media_id, code, &reload_shell/2, &append_output_entry/5)
|
||||
send_update(MediaEditor, id: "media-editor-#{media_id}", action: :translate, language: code)
|
||||
socket
|
||||
|
||||
_other ->
|
||||
socket
|
||||
@@ -890,16 +772,29 @@ defmodule BDS.Desktop.ShellLive do
|
||||
socket
|
||||
|
||||
{%{kind: :ai_suggestions} = overlay, %{type: :media, id: media_id}} ->
|
||||
MediaEditor.apply_ai_suggestions(
|
||||
socket,
|
||||
media_id,
|
||||
Overlay.selected_ai_fields(overlay),
|
||||
&reload_shell/2,
|
||||
&append_output_entry/5
|
||||
send_update(MediaEditor, id: "media-editor-#{media_id}",
|
||||
action: :apply_ai_suggestions,
|
||||
fields: Overlay.selected_ai_fields(overlay)
|
||||
)
|
||||
|
||||
socket
|
||||
|
||||
{%{kind: :confirm_delete}, %{type: :media, id: media_id}} ->
|
||||
MediaEditor.delete_socket(socket, media_id, &reload_shell/2, &append_output_entry/5)
|
||||
case Media.delete_media(media_id) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :media, media_id)
|
||||
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:media, media_id}))
|
||||
|> reload_shell(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(translated("Delete Media"), inspect(reason), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
{%{kind: :confirm_delete, title: title, entity_name: entity_name}, _tab} ->
|
||||
close_overlay_with_output(socket, title, entity_name)
|
||||
@@ -1304,6 +1199,28 @@ defmodule BDS.Desktop.ShellLive do
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_info({:media_editor_output, title, message, level}, socket) do
|
||||
{:noreply, append_output_entry(socket, title, message, nil, level)}
|
||||
end
|
||||
|
||||
def handle_info({:media_editor_dirty, media_id, dirty?}, socket) do
|
||||
workbench =
|
||||
if dirty? do
|
||||
Workbench.mark_dirty(socket.assigns.workbench, :media, media_id)
|
||||
else
|
||||
Workbench.clear_dirty(socket.assigns.workbench, :media, media_id)
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :workbench, workbench)}
|
||||
end
|
||||
|
||||
def handle_info({:media_editor_tab_meta, media_id, title, subtitle}, socket) do
|
||||
tab_meta =
|
||||
Map.put(socket.assigns.tab_meta, {:media, media_id}, %{title: title, subtitle: subtitle})
|
||||
|
||||
{:noreply, assign(socket, :tab_meta, tab_meta)}
|
||||
end
|
||||
|
||||
def handle_info(:reload_shell, socket) do
|
||||
{:noreply, reload_shell(socket, socket.assigns.workbench)}
|
||||
end
|
||||
@@ -1380,7 +1297,6 @@ defmodule BDS.Desktop.ShellLive do
|
||||
|> assign(:menu_groups, socket.assigns[:menu_groups] || TitlebarMenu.groups())
|
||||
|> assign(:titlebar_menu_item_index, socket.assigns[:titlebar_menu_item_index])
|
||||
|> assign(:current_tab, current_tab(workbench))
|
||||
|> assign_media_editor()
|
||||
|> assign_chat_editor()
|
||||
|> assign_import_editor()
|
||||
|> assign_misc_editor()
|
||||
@@ -1425,10 +1341,6 @@ defmodule BDS.Desktop.ShellLive do
|
||||
Enum.find(tabs, &(&1.type == type and &1.id == id))
|
||||
end
|
||||
|
||||
defp assign_media_editor(socket) do
|
||||
MediaEditor.assign_socket(socket)
|
||||
end
|
||||
|
||||
defp assign_chat_editor(socket) do
|
||||
ChatEditor.assign_socket(socket)
|
||||
end
|
||||
@@ -1594,7 +1506,8 @@ defmodule BDS.Desktop.ShellLive do
|
||||
end
|
||||
|
||||
defp save_current_tab(%{assigns: %{current_tab: %{type: :media, id: media_id}}} = socket) do
|
||||
MediaEditor.persist_socket(socket, media_id, &reload_shell/2, &append_output_entry/5)
|
||||
send_update(MediaEditor, id: "media-editor-#{media_id}", action: :save)
|
||||
socket
|
||||
end
|
||||
|
||||
defp save_current_tab(%{assigns: %{current_tab: %{type: :settings}}} = socket) do
|
||||
@@ -1740,9 +1653,21 @@ defmodule BDS.Desktop.ShellLive do
|
||||
end
|
||||
|
||||
"media" ->
|
||||
case Media.delete_media(id) do
|
||||
{:ok, :deleted} ->
|
||||
workbench = Workbench.close_tab(socket.assigns.workbench, :media, id)
|
||||
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> MediaEditor.delete_socket(id, &reload_shell/2, &append_output_entry/5)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:media, id}))
|
||||
|> reload_shell(workbench)
|
||||
|
||||
{:error, reason} ->
|
||||
socket
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> append_output_entry(translated("Delete Media"), inspect(reason), nil, "error")
|
||||
|> reload_shell(socket.assigns.workbench)
|
||||
end
|
||||
|
||||
"scripts" ->
|
||||
delete_sidebar_script(socket, id)
|
||||
|
||||
@@ -58,27 +58,6 @@ defmodule BDS.Desktop.ShellLive.CliSync do
|
||||
|> assign(:workbench, workbench)
|
||||
|> assign(:shell_overlay, nil)
|
||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {:media, media_id}))
|
||||
|> assign(:media_editor_drafts, Map.delete(socket.assigns.media_editor_drafts, media_id))
|
||||
|> assign(
|
||||
:media_editor_quick_actions_open,
|
||||
Map.delete(socket.assigns.media_editor_quick_actions_open, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_post_pickers_open,
|
||||
Map.delete(socket.assigns.media_editor_post_pickers_open, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_post_picker_queries,
|
||||
Map.delete(socket.assigns.media_editor_post_picker_queries, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_save_states,
|
||||
Map.delete(socket.assigns.media_editor_save_states, media_id)
|
||||
)
|
||||
|> assign(
|
||||
:media_editor_translation_forms,
|
||||
Map.delete(socket.assigns.media_editor_translation_forms, media_id)
|
||||
)
|
||||
|
||||
{socket, workbench}
|
||||
end
|
||||
|
||||
@@ -385,8 +385,8 @@
|
||||
<% @current_tab.type == :post -> %>
|
||||
<.live_component module={PostEditor} id={"post-editor-#{@current_tab.id}"} current_tab={@current_tab} offline_mode={@offline_mode} />
|
||||
|
||||
<% @current_tab.type == :media and @media_editor -> %>
|
||||
<MediaEditor.media_editor media_editor={@media_editor} />
|
||||
<% @current_tab.type == :media -> %>
|
||||
<.live_component module={MediaEditor} id={"media-editor-#{@current_tab.id}"} current_tab={@current_tab} offline_mode={@offline_mode} />
|
||||
|
||||
<% @current_tab.type in [:settings, :style] and @current_project -> %>
|
||||
<.live_component module={SettingsEditor} id="settings-editor"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
||||
class="secondary quick-actions-btn"
|
||||
type="button"
|
||||
phx-click="toggle_media_editor_quick_actions"
|
||||
phx-value-id={@media_editor.id}
|
||||
phx-target={@myself}
|
||||
>
|
||||
<span class="quick-actions-btn-icon">⚡</span>
|
||||
<span class="quick-actions-btn-label"><%= translated("Quick Actions") %></span>
|
||||
@@ -53,7 +53,7 @@
|
||||
class="quick-action-item"
|
||||
type="button"
|
||||
phx-click="detect_media_editor_language"
|
||||
phx-value-id={@media_editor.id}
|
||||
phx-target={@myself}
|
||||
disabled={not @media_editor.can_detect_language?}
|
||||
>
|
||||
<span class="quick-action-text">
|
||||
@@ -83,10 +83,10 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<button class="secondary" type="button" phx-click="replace_media_editor_file" phx-value-id={@media_editor.id}>
|
||||
<button class="secondary" type="button" phx-click="replace_media_editor_file" phx-target={@myself}>
|
||||
<%= translated("Replace File") %>
|
||||
</button>
|
||||
<button data-testid="media-save-button" type="button" phx-click="save_media_editor" phx-value-id={@media_editor.id}>
|
||||
<button data-testid="media-save-button" type="button" phx-click="save_media_editor" phx-target={@myself}>
|
||||
<%= translated("Save") %>
|
||||
</button>
|
||||
<button
|
||||
@@ -118,7 +118,7 @@
|
||||
</div>
|
||||
|
||||
<div class="media-details">
|
||||
<form class="media-editor-details-form" data-testid="media-editor-form" phx-change="change_media_editor">
|
||||
<form class="media-editor-details-form" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}>
|
||||
<div class="editor-field">
|
||||
<label><%= translated("File Name") %></label>
|
||||
<input class="post-editor-input disabled" type="text" value={@media_editor.original_name} disabled />
|
||||
@@ -193,15 +193,15 @@
|
||||
class="linked-post-title linked-post-link"
|
||||
type="button"
|
||||
phx-click="edit_media_translation"
|
||||
phx-value-id={@media_editor.id}
|
||||
phx-target={@myself}
|
||||
phx-value-language={translation.language}
|
||||
>
|
||||
<%= translation.flag %> <%= language_label(translation.language) %><%= if translation.title, do: " — #{translation.title}" %>
|
||||
</button>
|
||||
<button class="secondary compact" type="button" phx-click="refresh_media_translation" phx-value-id={@media_editor.id} phx-value-language={translation.language}>
|
||||
<button class="secondary compact" type="button" phx-click="refresh_media_translation" phx-target={@myself} phx-value-language={translation.language}>
|
||||
<%= translated("Refresh") %>
|
||||
</button>
|
||||
<button class="unlink-btn" type="button" phx-click="delete_media_translation" phx-value-id={@media_editor.id} phx-value-language={translation.language}>×</button>
|
||||
<button class="unlink-btn" type="button" phx-click="delete_media_translation" phx-target={@myself} phx-value-language={translation.language}>×</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -212,7 +212,7 @@
|
||||
<div class="editor-field linked-posts-section">
|
||||
<label>
|
||||
<%= translated("Linked Posts") %>
|
||||
<button class="add-link-btn" type="button" phx-click="toggle_media_post_picker" phx-value-id={@media_editor.id}>
|
||||
<button class="add-link-btn" type="button" phx-click="toggle_media_post_picker" phx-target={@myself}>
|
||||
<%= translated("Link to Post") %>
|
||||
</button>
|
||||
</label>
|
||||
@@ -226,7 +226,7 @@
|
||||
value={@media_editor.post_picker_query}
|
||||
placeholder={translated("Search posts")}
|
||||
phx-change="change_media_post_picker"
|
||||
phx-value-id={@media_editor.id}
|
||||
phx-target={@myself}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
<% else %>
|
||||
<div class="post-picker-list">
|
||||
<%= for result <- @media_editor.post_picker_results do %>
|
||||
<button class="post-picker-item" type="button" phx-click="link_media_to_post" phx-value-id={@media_editor.id} phx-value-post-id={result.post_id}>
|
||||
<button class="post-picker-item" type="button" phx-click="link_media_to_post" phx-target={@myself} phx-value-post-id={result.post_id}>
|
||||
<%= result.title %>
|
||||
</button>
|
||||
<% end %>
|
||||
@@ -264,7 +264,7 @@
|
||||
>
|
||||
📄 <%= linked_post.title %>
|
||||
</button>
|
||||
<button class="unlink-btn" type="button" phx-click="unlink_media_from_post" phx-value-id={@media_editor.id} phx-value-post-id={linked_post.post_id}>×</button>
|
||||
<button class="unlink-btn" type="button" phx-click="unlink_media_from_post" phx-target={@myself} phx-value-post-id={linked_post.post_id}>×</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -278,9 +278,9 @@
|
||||
<div class="translation-modal">
|
||||
<div class="translation-modal-header">
|
||||
<h2><%= translated("Edit Translation") %></h2>
|
||||
<button class="translation-modal-close" type="button" phx-click="close_media_translation_editor">×</button>
|
||||
<button class="translation-modal-close" type="button" phx-click="close_media_translation_editor" phx-target={@myself}>×</button>
|
||||
</div>
|
||||
<form class="translation-modal-body" phx-change="change_media_translation">
|
||||
<form class="translation-modal-body" phx-change="change_media_translation" phx-target={@myself}>
|
||||
<input type="hidden" name="media_translation[language]" value={@media_editor.editing_translation["language"]} />
|
||||
<div class="editor-field">
|
||||
<label><%= translated("Title") %></label>
|
||||
@@ -296,8 +296,8 @@
|
||||
</div>
|
||||
</form>
|
||||
<div class="translation-modal-footer">
|
||||
<button class="secondary" type="button" phx-click="close_media_translation_editor"><%= translated("Cancel") %></button>
|
||||
<button type="button" phx-click="save_media_translation" phx-value-id={@media_editor.id}><%= translated("Save") %></button>
|
||||
<button class="secondary" type="button" phx-click="close_media_translation_editor" phx-target={@myself}><%= translated("Cancel") %></button>
|
||||
<button type="button" phx-click="save_media_translation" phx-target={@myself}><%= translated("Save") %></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2444,7 +2444,11 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
|
||||
assert html =~ ~s(data-testid="media-editor")
|
||||
|
||||
html = render_click(view, "toggle_media_editor_quick_actions", %{"id" => media.id})
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='media-editor'] .quick-actions-btn")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "quick-actions-menu"
|
||||
|
||||
html =
|
||||
@@ -2705,7 +2709,10 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
assert html =~ "Titelbild"
|
||||
refute html =~ "Desktop workbench content routed through the Elixir shell."
|
||||
|
||||
html = render_click(view, "toggle_media_editor_quick_actions", %{"id" => media.id})
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='media-editor'] .quick-actions-btn")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "quick-actions-menu"
|
||||
assert html =~ "Detect Language"
|
||||
@@ -2727,7 +2734,10 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
|
||||
assert html =~ "Updated Cover"
|
||||
|
||||
_html = render_click(view, "save_media_editor", %{"id" => media.id})
|
||||
_html =
|
||||
view
|
||||
|> element("[data-testid='media-save-button']")
|
||||
|> render_click()
|
||||
|
||||
saved_media = Repo.get!(BDS.Media.Media, media.id)
|
||||
assert saved_media.title == "Updated Cover"
|
||||
@@ -2791,7 +2801,10 @@ defmodule BDS.Desktop.ShellLiveTest do
|
||||
"[data-testid='media-editor'] .editor-content.media-editor .media-details .linked-posts-section"
|
||||
)
|
||||
|
||||
html = render_click(view, "edit_media_translation", %{"id" => media.id, "language" => "de"})
|
||||
html =
|
||||
view
|
||||
|> element("[phx-click='edit_media_translation'][phx-value-language='de']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ ~s(class="translation-modal-backdrop")
|
||||
assert html =~ ~s(class="translation-modal")
|
||||
|
||||
@@ -401,8 +401,8 @@ defmodule BDS.UI.ShellTest do
|
||||
media_editor_ex =
|
||||
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/media_editor.ex")
|
||||
|
||||
assert template =~ "<MediaEditor.media_editor"
|
||||
assert media_editor_ex =~ "def build(%{current_tab: %{type: :media, id: media_id}} = assigns)"
|
||||
assert template =~ "<.live_component module={MediaEditor}"
|
||||
assert media_editor_ex =~ "defp build_data(socket)"
|
||||
|
||||
refute live_ex =~ "defp update_media_editor("
|
||||
refute live_ex =~ "defp persist_media_editor("
|
||||
|
||||
Reference in New Issue
Block a user