Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions lib/modules/posts/web/edit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ defmodule PhoenixKitWeb.Live.Modules.Posts.Edit do
@impl true
def handle_event("save", %{"post" => post_params}, socket) do
# Merge content from assigns (stored separately from form)
content = socket.assigns.content
content = socket.assigns[:live_content] || socket.assigns.content
post_params = Map.put(post_params, "content", content)

# Parse tags from content if auto-tagging is enabled
Expand Down Expand Up @@ -215,14 +215,13 @@ defmodule PhoenixKitWeb.Live.Modules.Posts.Edit do
end
end

# Handle content changes from MarkdownEditor component
# Handle content changes from MarkdownEditor/Leaf component
@impl true
def handle_info(
{:editor_content_changed, %{content: content, editor_id: "post-content-editor"}},
{:editor_content_changed, %{content: content, editor_id: "post-content-editor" <> _}},
socket
) do
# Store content as a separate assign (like Publishing editor does)
{:noreply, assign(socket, :content, content)}
{:noreply, assign(socket, :live_content, content)}
end

# Handle image/video insert from MarkdownEditor toolbar
Expand Down Expand Up @@ -324,6 +323,17 @@ defmodule PhoenixKitWeb.Live.Modules.Posts.Edit do
|> assign(:selecting_featured_image, false)}
end

# Handle Leaf editor messages
def handle_info({:leaf_changed, %{markdown: content}}, socket) do
{:noreply, assign(socket, :live_content, content)}
end

def handle_info({:leaf_insert_request, %{type: type}}, socket) do
handle_info({:editor_insert_component, %{type: type}}, socket)
end

def handle_info({:leaf_mode_changed, _}, socket), do: {:noreply, socket}

# Catch-all for other editor events
def handle_info({:editor_insert_component, _}, socket), do: {:noreply, socket}
def handle_info({:editor_save_requested, _}, socket), do: {:noreply, socket}
Expand Down
27 changes: 18 additions & 9 deletions lib/modules/posts/web/edit.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -143,15 +143,14 @@
</span>
</label>
<.live_component
module={PhoenixKitWeb.Components.Core.MarkdownEditor}
module={Leaf}
id="post-content-editor"
content={@content}
mode={:visual}
placeholder="Write your post content here... Use #hashtags for auto-tagging"
height="400px"
debounce={400}
toolbar={[:image, :video]}
protect_navigation={false}
script_nonce={assigns[:csp_nonce] || ""}
/>
<label class="label">
<span class="label-text-alt text-base-content/70">
Expand Down Expand Up @@ -471,12 +470,24 @@
/>

<script>
// Function to insert standard markdown media syntax at cursor position
// Function to insert media into Leaf editor at cursor position
window.postsEditorInsertMedia = function(fileUrl, mediaType) {
const textarea = document.getElementById('post-content-editor-textarea');
// Try visual mode first (contenteditable div)
const visualEl = document.getElementById('post-content-editor-visual');
if (visualEl) {
visualEl.focus();
const altText = mediaType === 'image' ? 'Image description' : 'Video';
const html = '<img src="' + fileUrl + '" alt="' + altText + '" />';
document.execCommand('insertHTML', false, html);
// Trigger input event so Leaf picks up the change
visualEl.dispatchEvent(new Event('input', { bubbles: true }));
return;
}

// Markdown mode fallback
const textarea = document.getElementById('post-content-editor-markdown-textarea');
if (!textarea) return;

// Build standard markdown syntax: ![alt](url)
const altText = mediaType === 'image' ? 'Image description' : 'Video';
const template = '![' + altText + '](' + fileUrl + ')';

Expand All @@ -487,13 +498,11 @@
const newValue = currentValue.substring(0, start) + template + currentValue.substring(end);
textarea.value = newValue;

// Move cursor after inserted text
const newPos = start + template.length;
textarea.selectionStart = textarea.selectionEnd = newPos;
textarea.focus();

// Trigger keyup event to update LiveView
textarea.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true }));
textarea.dispatchEvent(new Event('input', { bubbles: true }));
};

// Handle exec-js event from server
Expand Down
3 changes: 3 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ defmodule PhoenixKit.MixProject do
{:floki, ">= 0.30.0", only: :test},
{:hackney, "~> 1.9"},

# Content editor
{:leaf, "~> 0.1.0"},

# Utilities
{:jason, "~> 1.4"},
{:earmark, "~> 1.4"},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"igniter": {:hex, :igniter, "0.7.1", "ac006dc954de0760e5d284a465f7e68a919190232b61dd1616df0e1c8d8e3a0f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "c3100063d73bc3015c6dbc029c115f4d382e3332e77035cc9f708a9731ce7ea4"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"},
"leaf": {:hex, :leaf, "0.1.0", "36bd03f658ec9b53b41d9858211ce3126e282b6d1c3f43fecaed998f26b2af4e", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26 or ~> 1.0", [hex: :gettext, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "2d1782852deb20432773e53d24ebd905476d2a1f9f1981609fed648799d07498"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"},
Expand Down
Loading
Loading