refactor: move pure post-editor logic to BDS.UI.PostEditor for TUI reuse (issue #26, phase 3)
This commit is contained in:
@@ -6,11 +6,11 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
alias BDS.{AI, Embeddings, Metadata, Posts, Preview, Tasks}
|
alias BDS.{AI, Embeddings, Metadata, Posts, Preview, Tasks}
|
||||||
alias BDS.Desktop.ShellData
|
alias BDS.Desktop.ShellData
|
||||||
alias BDS.Desktop.ShellLive.{EditorImageDrop, Notify}
|
alias BDS.Desktop.ShellLive.{EditorImageDrop, Notify}
|
||||||
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, ListValues, Persistence, PostMetadata}
|
|
||||||
alias BDS.Posts.Post
|
alias BDS.Posts.Post
|
||||||
alias BDS.Tags
|
alias BDS.Tags
|
||||||
|
alias BDS.UI.PostEditor.{Draft, ListValues, Persistence}
|
||||||
|
|
||||||
import DraftManagement,
|
import Draft,
|
||||||
only: [
|
only: [
|
||||||
editing_canonical_language?: 3,
|
editing_canonical_language?: 3,
|
||||||
normalize_language: 2,
|
normalize_language: 2,
|
||||||
@@ -48,7 +48,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do
|
|||||||
|
|
||||||
use Gettext, backend: BDS.Gettext
|
use Gettext, backend: BDS.Gettext
|
||||||
|
|
||||||
import PostMetadata,
|
import BDS.UI.PostEditor.Metadata,
|
||||||
only: [
|
only: [
|
||||||
blank?: 1,
|
blank?: 1,
|
||||||
blank_to_nil: 1,
|
blank_to_nil: 1,
|
||||||
|
|||||||
@@ -1,241 +0,0 @@
|
|||||||
defmodule BDS.Desktop.ShellLive.PostEditor.DraftManagement do
|
|
||||||
@moduledoc false
|
|
||||||
|
|
||||||
import Phoenix.Component, only: [assign: 3]
|
|
||||||
|
|
||||||
alias BDS.Posts
|
|
||||||
alias BDS.Posts.{Post, Translation}
|
|
||||||
alias BDS.Desktop.ShellLive.PostEditor.PostMetadata
|
|
||||||
alias BDS.UI.Workbench
|
|
||||||
|
|
||||||
@spec normalize_mode(term()) :: term()
|
|
||||||
def normalize_mode(mode) when mode in [:markdown, :preview], do: mode
|
|
||||||
@spec normalize_mode(term()) :: term()
|
|
||||||
def normalize_mode("visual"), do: :markdown
|
|
||||||
def normalize_mode("preview"), do: :preview
|
|
||||||
def normalize_mode(_mode), do: :markdown
|
|
||||||
|
|
||||||
@spec normalize_language(term(), term()) :: term()
|
|
||||||
def normalize_language(value, fallback) do
|
|
||||||
case value |> to_string() |> String.trim() do
|
|
||||||
"" -> fallback
|
|
||||||
normalized -> String.downcase(normalized)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec normalize_params(term(), term(), term()) :: term()
|
|
||||||
def normalize_params(params, current_language, next_language) do
|
|
||||||
%{
|
|
||||||
"title" => Map.get(params, "title", ""),
|
|
||||||
"excerpt" => Map.get(params, "excerpt", ""),
|
|
||||||
"content" => Map.get(params, "content", ""),
|
|
||||||
"tags" => Map.get(params, "tags", ""),
|
|
||||||
"categories" => Map.get(params, "categories", ""),
|
|
||||||
"author" => Map.get(params, "author", ""),
|
|
||||||
"language" =>
|
|
||||||
if(current_language == next_language,
|
|
||||||
do: normalize_language(Map.get(params, "language"), current_language),
|
|
||||||
else: next_language
|
|
||||||
),
|
|
||||||
"do_not_translate" => truthy?(Map.get(params, "do_not_translate")),
|
|
||||||
"template_slug" => Map.get(params, "template_slug", "")
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec current_draft(term(), term(), term(), term()) :: term()
|
|
||||||
def current_draft(assigns, %Post{} = post, metadata, active_language) do
|
|
||||||
persisted = persisted_form(post, metadata, active_language)
|
|
||||||
|
|
||||||
assigns.post_editor_drafts
|
|
||||||
|> Map.get(post.id, %{})
|
|
||||||
|> Map.get(active_language, persisted)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec persisted_form(term(), term(), term()) :: term()
|
|
||||||
def persisted_form(%Post{} = post, metadata, active_language) do
|
|
||||||
persisted_form(post, metadata, active_language, PostMetadata.translations(post.id))
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec persisted_form(term(), term(), term(), term()) :: term()
|
|
||||||
def persisted_form(post, metadata, active_language, translations) do
|
|
||||||
canonical_language = PostMetadata.canonical_language(post, metadata)
|
|
||||||
translation = Map.get(translations, active_language)
|
|
||||||
|
|
||||||
if active_language == canonical_language do
|
|
||||||
%{
|
|
||||||
"title" => post.title || "",
|
|
||||||
"excerpt" => post.excerpt || "",
|
|
||||||
"content" => Posts.editor_body(post),
|
|
||||||
"tags" => Enum.join(post.tags || [], ", "),
|
|
||||||
"categories" => Enum.join(post.categories || [], ", "),
|
|
||||||
"author" => post.author || metadata.default_author || "",
|
|
||||||
"language" => canonical_language,
|
|
||||||
"do_not_translate" => post.do_not_translate || false,
|
|
||||||
"template_slug" => post.template_slug || ""
|
|
||||||
}
|
|
||||||
else
|
|
||||||
%{
|
|
||||||
"title" => (translation && translation.title) || "",
|
|
||||||
"excerpt" => (translation && translation.excerpt) || "",
|
|
||||||
"content" => if(translation, do: Posts.editor_body(translation), else: ""),
|
|
||||||
"tags" => Enum.join(post.tags || [], ", "),
|
|
||||||
"categories" => Enum.join(post.categories || [], ", "),
|
|
||||||
"author" => post.author || metadata.default_author || "",
|
|
||||||
"language" => active_language,
|
|
||||||
"do_not_translate" => post.do_not_translate || false,
|
|
||||||
"template_slug" => post.template_slug || ""
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec maybe_update_draft(term(), term(), term(), term(), term(), term(), term()) :: term()
|
|
||||||
def maybe_update_draft(socket, post_id, post, current_language, next_language, draft, true) do
|
|
||||||
workbench = Workbench.mark_dirty(socket.assigns.workbench, :post, post_id)
|
|
||||||
|
|
||||||
socket
|
|
||||||
|> assign(:workbench, workbench)
|
|
||||||
|> assign(
|
|
||||||
:post_editor_drafts,
|
|
||||||
put_nested_map(socket.assigns.post_editor_drafts, post_id, next_language, draft)
|
|
||||||
)
|
|
||||||
|> assign(
|
|
||||||
:post_editor_active_languages,
|
|
||||||
Map.put(socket.assigns.post_editor_active_languages, post_id, next_language)
|
|
||||||
)
|
|
||||||
|> assign(
|
|
||||||
:post_editor_save_states,
|
|
||||||
Map.put(socket.assigns.post_editor_save_states, post_id, :dirty)
|
|
||||||
)
|
|
||||||
|> assign(
|
|
||||||
:tab_meta,
|
|
||||||
Map.put(socket.assigns.tab_meta, {:post, post_id}, %{
|
|
||||||
title: draft["title"],
|
|
||||||
subtitle: Atom.to_string(post.status || :draft)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|> maybe_drop_old_language_draft(post_id, current_language, next_language)
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_update_draft(socket, post_id, _post, _current_language, next_language, _draft, false) do
|
|
||||||
assign(
|
|
||||||
socket,
|
|
||||||
:post_editor_active_languages,
|
|
||||||
Map.put(socket.assigns.post_editor_active_languages, post_id, next_language)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec put_draft_field(term(), term(), term(), term(), term(), term()) :: term()
|
|
||||||
def put_draft_field(socket, post_id, post, active_language, field, value) do
|
|
||||||
metadata = PostMetadata.project_metadata(post.project_id)
|
|
||||||
draft = Map.put(current_draft(socket.assigns, post, metadata, active_language), field, value)
|
|
||||||
workbench = Workbench.mark_dirty(socket.assigns.workbench, :post, post_id)
|
|
||||||
|
|
||||||
socket
|
|
||||||
|> assign(:workbench, workbench)
|
|
||||||
|> assign(
|
|
||||||
:post_editor_drafts,
|
|
||||||
put_nested_map(socket.assigns.post_editor_drafts, post_id, active_language, draft)
|
|
||||||
)
|
|
||||||
|> assign(
|
|
||||||
:post_editor_save_states,
|
|
||||||
Map.put(socket.assigns.post_editor_save_states, post_id, :dirty)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec put_query_state(term(), term(), term(), term()) :: term()
|
|
||||||
def put_query_state(socket, post_id, kind, value) do
|
|
||||||
key = query_key(kind)
|
|
||||||
|
|
||||||
assign(
|
|
||||||
socket,
|
|
||||||
key,
|
|
||||||
Map.put(Map.get(socket.assigns, key, %{}), post_id, to_string(value || ""))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec query_value(term(), term(), term()) :: term()
|
|
||||||
def query_value(assigns, kind, post_id) do
|
|
||||||
assigns
|
|
||||||
|> Map.get(query_key(kind), %{})
|
|
||||||
|> Map.get(post_id, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
defp query_key(:tags), do: :post_editor_tag_queries
|
|
||||||
defp query_key(:categories), do: :post_editor_category_queries
|
|
||||||
|
|
||||||
defp maybe_drop_old_language_draft(socket, _post_id, current_language, next_language)
|
|
||||||
when current_language == next_language,
|
|
||||||
do: socket
|
|
||||||
|
|
||||||
defp maybe_drop_old_language_draft(socket, post_id, current_language, _next_language) do
|
|
||||||
assign(
|
|
||||||
socket,
|
|
||||||
:post_editor_drafts,
|
|
||||||
delete_nested_map(socket.assigns.post_editor_drafts, post_id, current_language)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec toggled_sections(term(), term(), term()) :: term()
|
|
||||||
def toggled_sections(expanded_by_post, post_id, section) do
|
|
||||||
expanded_by_post
|
|
||||||
|> Map.get(post_id, %{metadata: false, excerpt: false})
|
|
||||||
|> Map.put_new(:metadata, false)
|
|
||||||
|> Map.put_new(:excerpt, false)
|
|
||||||
|> Map.update!(section, &(not &1))
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec put_nested_map(term(), term(), term(), term()) :: term()
|
|
||||||
def put_nested_map(map, key, nested_key, value) do
|
|
||||||
Map.update(map, key, %{nested_key => value}, &Map.put(&1, nested_key, value))
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec delete_nested_map(term(), term(), term()) :: term()
|
|
||||||
def delete_nested_map(map, key, nested_key) do
|
|
||||||
case Map.get(map, key) do
|
|
||||||
nil ->
|
|
||||||
map
|
|
||||||
|
|
||||||
nested ->
|
|
||||||
case Map.delete(nested, nested_key) do
|
|
||||||
emptied when map_size(emptied) == 0 -> Map.delete(map, key)
|
|
||||||
remaining -> Map.put(map, key, remaining)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec reload_with_assigned_workbench(term(), term()) :: term()
|
|
||||||
def reload_with_assigned_workbench(socket, reload),
|
|
||||||
do: reload.(socket, socket.assigns.workbench)
|
|
||||||
|
|
||||||
@spec save_state_for_action(term()) :: term()
|
|
||||||
def save_state_for_action(:publish), do: :published
|
|
||||||
def save_state_for_action(_action), do: :saved
|
|
||||||
|
|
||||||
@spec record_title(term(), term()) :: term()
|
|
||||||
def record_title(%Translation{title: title}, post),
|
|
||||||
do: blank_to_nil(title) || post.title || post.slug || post.id
|
|
||||||
|
|
||||||
def record_title(%Post{title: title, slug: slug, id: id}, _post),
|
|
||||||
do: blank_to_nil(title) || blank_to_nil(slug) || id
|
|
||||||
|
|
||||||
@spec record_status(term()) :: term()
|
|
||||||
def record_status(%Translation{status: status}), do: status || :draft
|
|
||||||
def record_status(%Post{status: status}), do: status || :draft
|
|
||||||
|
|
||||||
@spec editing_canonical_language?(term(), term(), term()) :: term()
|
|
||||||
def editing_canonical_language?(translations, active_language, canonical_language) do
|
|
||||||
active_language == canonical_language or not Map.has_key?(translations, active_language)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp truthy?(value), do: BDS.Values.truthy?(value)
|
|
||||||
|
|
||||||
defp blank_to_nil(value) do
|
|
||||||
value
|
|
||||||
|> to_string()
|
|
||||||
|> String.trim()
|
|
||||||
|> case do
|
|
||||||
"" -> nil
|
|
||||||
trimmed -> trimmed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
107
lib/bds/ui/post_editor/draft.ex
Normal file
107
lib/bds/ui/post_editor/draft.ex
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
defmodule BDS.UI.PostEditor.Draft do
|
||||||
|
@moduledoc """
|
||||||
|
Pure draft/form logic for the post editor (issue #26, phase 3).
|
||||||
|
|
||||||
|
Shared by the LiveView shell and the TUI: builds the persisted form for a
|
||||||
|
post (canonical language or translation), normalizes editor params, and
|
||||||
|
provides the small pure helpers both renderers need. No socket, no
|
||||||
|
process state — callers own the draft maps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias BDS.Posts
|
||||||
|
alias BDS.Posts.{Post, Translation}
|
||||||
|
alias BDS.UI.PostEditor.Metadata
|
||||||
|
|
||||||
|
def normalize_mode(mode) when mode in [:markdown, :preview], do: mode
|
||||||
|
def normalize_mode("visual"), do: :markdown
|
||||||
|
def normalize_mode("preview"), do: :preview
|
||||||
|
def normalize_mode(_mode), do: :markdown
|
||||||
|
|
||||||
|
def normalize_language(value, fallback) do
|
||||||
|
case value |> to_string() |> String.trim() do
|
||||||
|
"" -> fallback
|
||||||
|
normalized -> String.downcase(normalized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_params(params, current_language, next_language) do
|
||||||
|
%{
|
||||||
|
"title" => Map.get(params, "title", ""),
|
||||||
|
"excerpt" => Map.get(params, "excerpt", ""),
|
||||||
|
"content" => Map.get(params, "content", ""),
|
||||||
|
"tags" => Map.get(params, "tags", ""),
|
||||||
|
"categories" => Map.get(params, "categories", ""),
|
||||||
|
"author" => Map.get(params, "author", ""),
|
||||||
|
"language" =>
|
||||||
|
if(current_language == next_language,
|
||||||
|
do: normalize_language(Map.get(params, "language"), current_language),
|
||||||
|
else: next_language
|
||||||
|
),
|
||||||
|
"do_not_translate" => truthy?(Map.get(params, "do_not_translate")),
|
||||||
|
"template_slug" => Map.get(params, "template_slug", "")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def persisted_form(%Post{} = post, metadata, active_language) do
|
||||||
|
persisted_form(post, metadata, active_language, Metadata.translations(post.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def persisted_form(post, metadata, active_language, translations) do
|
||||||
|
canonical_language = Metadata.canonical_language(post, metadata)
|
||||||
|
translation = Map.get(translations, active_language)
|
||||||
|
|
||||||
|
if active_language == canonical_language do
|
||||||
|
%{
|
||||||
|
"title" => post.title || "",
|
||||||
|
"excerpt" => post.excerpt || "",
|
||||||
|
"content" => Posts.editor_body(post),
|
||||||
|
"tags" => Enum.join(post.tags || [], ", "),
|
||||||
|
"categories" => Enum.join(post.categories || [], ", "),
|
||||||
|
"author" => post.author || metadata.default_author || "",
|
||||||
|
"language" => canonical_language,
|
||||||
|
"do_not_translate" => post.do_not_translate || false,
|
||||||
|
"template_slug" => post.template_slug || ""
|
||||||
|
}
|
||||||
|
else
|
||||||
|
%{
|
||||||
|
"title" => (translation && translation.title) || "",
|
||||||
|
"excerpt" => (translation && translation.excerpt) || "",
|
||||||
|
"content" => if(translation, do: Posts.editor_body(translation), else: ""),
|
||||||
|
"tags" => Enum.join(post.tags || [], ", "),
|
||||||
|
"categories" => Enum.join(post.categories || [], ", "),
|
||||||
|
"author" => post.author || metadata.default_author || "",
|
||||||
|
"language" => active_language,
|
||||||
|
"do_not_translate" => post.do_not_translate || false,
|
||||||
|
"template_slug" => post.template_slug || ""
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_state_for_action(:publish), do: :published
|
||||||
|
def save_state_for_action(_action), do: :saved
|
||||||
|
|
||||||
|
def record_title(%Translation{title: title}, post),
|
||||||
|
do: blank_to_nil(title) || post.title || post.slug || post.id
|
||||||
|
|
||||||
|
def record_title(%Post{title: title, slug: slug, id: id}, _post),
|
||||||
|
do: blank_to_nil(title) || blank_to_nil(slug) || id
|
||||||
|
|
||||||
|
def record_status(%Translation{status: status}), do: status || :draft
|
||||||
|
def record_status(%Post{status: status}), do: status || :draft
|
||||||
|
|
||||||
|
def editing_canonical_language?(translations, active_language, canonical_language) do
|
||||||
|
active_language == canonical_language or not Map.has_key?(translations, active_language)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp truthy?(value), do: BDS.Values.truthy?(value)
|
||||||
|
|
||||||
|
defp blank_to_nil(value) do
|
||||||
|
value
|
||||||
|
|> to_string()
|
||||||
|
|> String.trim()
|
||||||
|
|> case do
|
||||||
|
"" -> nil
|
||||||
|
trimmed -> trimmed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
defmodule BDS.Desktop.ShellLive.PostEditor.ListValues do
|
defmodule BDS.UI.PostEditor.ListValues do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
alias BDS.{Metadata, Tags}
|
alias BDS.{Metadata, Tags}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
defmodule BDS.UI.PostEditor.Metadata do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
@@ -20,7 +20,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do
|
|||||||
|
|
||||||
@spec canonical_language(term(), term()) :: term()
|
@spec canonical_language(term(), term()) :: term()
|
||||||
def canonical_language(post, metadata) do
|
def canonical_language(post, metadata) do
|
||||||
BDS.Desktop.ShellLive.PostEditor.DraftManagement.normalize_language(
|
BDS.UI.PostEditor.Draft.normalize_language(
|
||||||
post.language,
|
post.language,
|
||||||
metadata.main_language || "en"
|
metadata.main_language || "en"
|
||||||
)
|
)
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
defmodule BDS.UI.PostEditor.Persistence do
|
||||||
@moduledoc false
|
@moduledoc """
|
||||||
|
Save/publish/discard workflow for post drafts, shared by the LiveView
|
||||||
|
shell and the TUI (issue #26, phase 3). Routes canonical-language edits
|
||||||
|
to the post itself and other languages to translations.
|
||||||
|
"""
|
||||||
|
|
||||||
alias BDS.Posts
|
alias BDS.Posts
|
||||||
alias BDS.Posts.Post
|
alias BDS.Posts.Post
|
||||||
alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, PostMetadata}
|
alias BDS.UI.PostEditor.{Draft, Metadata}
|
||||||
use Gettext, backend: BDS.Gettext
|
use Gettext, backend: BDS.Gettext
|
||||||
|
|
||||||
@spec persist(term(), term(), term(), term(), term()) :: term()
|
@spec persist(term(), term(), term(), term(), term()) :: term()
|
||||||
def persist(%Post{} = post, draft, active_language, metadata, action) do
|
def persist(%Post{} = post, draft, active_language, metadata, action) do
|
||||||
canonical_language = PostMetadata.canonical_language(post, metadata)
|
canonical_language = Metadata.canonical_language(post, metadata)
|
||||||
translations = PostMetadata.translations(post.id)
|
translations = Metadata.translations(post.id)
|
||||||
|
|
||||||
if DraftManagement.editing_canonical_language?(
|
if Draft.editing_canonical_language?(
|
||||||
translations,
|
translations,
|
||||||
active_language,
|
active_language,
|
||||||
canonical_language
|
canonical_language
|
||||||
@@ -28,11 +32,11 @@ defmodule BDS.Desktop.ShellLive.PostEditor.Persistence do
|
|||||||
|
|
||||||
@spec discard(term(), term(), term()) :: term()
|
@spec discard(term(), term(), term()) :: term()
|
||||||
def discard(%Post{} = post, active_language, metadata) do
|
def discard(%Post{} = post, active_language, metadata) do
|
||||||
canonical_language = PostMetadata.canonical_language(post, metadata)
|
canonical_language = Metadata.canonical_language(post, metadata)
|
||||||
current_translations = PostMetadata.translations(post.id)
|
current_translations = Metadata.translations(post.id)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
not DraftManagement.editing_canonical_language?(
|
not Draft.editing_canonical_language?(
|
||||||
current_translations,
|
current_translations,
|
||||||
active_language,
|
active_language,
|
||||||
canonical_language
|
canonical_language
|
||||||
@@ -55,43 +55,43 @@ defmodule BDS.GalleryPipelineTest do
|
|||||||
test "counts [[gallery]] macro as gallery content" do
|
test "counts [[gallery]] macro as gallery content" do
|
||||||
form = %{"content" => "Some text\n\n[[gallery]]\n\nMore text"}
|
form = %{"content" => "Some text\n\n[[gallery]]\n\nMore text"}
|
||||||
|
|
||||||
assert BDS.Desktop.ShellLive.PostEditor.PostMetadata.gallery_count(form) == 1
|
assert BDS.UI.PostEditor.Metadata.gallery_count(form) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
test "counts inline images as before" do
|
test "counts inline images as before" do
|
||||||
form = %{"content" => ""}
|
form = %{"content" => ""}
|
||||||
|
|
||||||
assert BDS.Desktop.ShellLive.PostEditor.PostMetadata.gallery_count(form) == 1
|
assert BDS.UI.PostEditor.Metadata.gallery_count(form) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
test "counts both [[gallery]] and inline images" do
|
test "counts both [[gallery]] and inline images" do
|
||||||
form = %{"content" => "\n[[gallery]]"}
|
form = %{"content" => "\n[[gallery]]"}
|
||||||
|
|
||||||
assert BDS.Desktop.ShellLive.PostEditor.PostMetadata.gallery_count(form) == 1
|
assert BDS.UI.PostEditor.Metadata.gallery_count(form) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns 0 when no gallery marks present" do
|
test "returns 0 when no gallery marks present" do
|
||||||
form = %{"content" => "Just plain text"}
|
form = %{"content" => "Just plain text"}
|
||||||
|
|
||||||
assert BDS.Desktop.ShellLive.PostEditor.PostMetadata.gallery_count(form) == 0
|
assert BDS.UI.PostEditor.Metadata.gallery_count(form) == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handles empty content" do
|
test "handles empty content" do
|
||||||
form = %{}
|
form = %{}
|
||||||
|
|
||||||
assert BDS.Desktop.ShellLive.PostEditor.PostMetadata.gallery_count(form) == 0
|
assert BDS.UI.PostEditor.Metadata.gallery_count(form) == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
test "counts multiple [[gallery]] occurrences" do
|
test "counts multiple [[gallery]] occurrences" do
|
||||||
form = %{"content" => "[[gallery]] some text [[gallery]]"}
|
form = %{"content" => "[[gallery]] some text [[gallery]]"}
|
||||||
|
|
||||||
assert BDS.Desktop.ShellLive.PostEditor.PostMetadata.gallery_count(form) == 2
|
assert BDS.UI.PostEditor.Metadata.gallery_count(form) == 2
|
||||||
end
|
end
|
||||||
|
|
||||||
test "is case insensitive for [[gallery]]" do
|
test "is case insensitive for [[gallery]]" do
|
||||||
form = %{"content" => "[[Gallery]]"}
|
form = %{"content" => "[[Gallery]]"}
|
||||||
|
|
||||||
assert BDS.Desktop.ShellLive.PostEditor.PostMetadata.gallery_count(form) == 1
|
assert BDS.UI.PostEditor.Metadata.gallery_count(form) == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ defmodule BDS.PostTranslationsTest do
|
|||||||
"template_slug" => ""
|
"template_slug" => ""
|
||||||
}
|
}
|
||||||
|
|
||||||
alias BDS.Desktop.ShellLive.PostEditor.Persistence
|
alias BDS.UI.PostEditor.Persistence
|
||||||
|
|
||||||
assert {:ok, _post} = Persistence.persist(post, draft, "en", metadata, :auto_save)
|
assert {:ok, _post} = Persistence.persist(post, draft, "en", metadata, :auto_save)
|
||||||
assert ai_tasks() == []
|
assert ai_tasks() == []
|
||||||
|
|||||||
64
test/bds/ui/post_editor_test.exs
Normal file
64
test/bds/ui/post_editor_test.exs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
defmodule BDS.UI.PostEditorTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
|
||||||
|
alias BDS.UI.PostEditor.{Draft, Metadata, Persistence}
|
||||||
|
|
||||||
|
setup do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||||
|
|
||||||
|
temp_dir = Path.join(System.tmp_dir!(), "bds-ui-pe-#{System.unique_integer([:positive])}")
|
||||||
|
File.mkdir_p!(temp_dir)
|
||||||
|
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||||
|
|
||||||
|
{:ok, project} = BDS.Projects.create_project(%{name: "UI PostEditor", data_path: temp_dir})
|
||||||
|
|
||||||
|
{:ok, post} =
|
||||||
|
BDS.Posts.create_post(%{
|
||||||
|
project_id: project.id,
|
||||||
|
title: "First Post",
|
||||||
|
content: "hello world",
|
||||||
|
tags: ["elixir"],
|
||||||
|
language: "en"
|
||||||
|
})
|
||||||
|
|
||||||
|
%{project: project, post: post}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "persisted_form builds the canonical draft", %{post: post} do
|
||||||
|
metadata = Metadata.project_metadata(post.project_id)
|
||||||
|
form = Draft.persisted_form(post, metadata, "en")
|
||||||
|
|
||||||
|
assert form["title"] == "First Post"
|
||||||
|
assert form["content"] == "hello world"
|
||||||
|
assert form["tags"] == "elixir"
|
||||||
|
assert form["language"] == "en"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "persist saves an edited draft and publish publishes it", %{post: post} do
|
||||||
|
metadata = Metadata.project_metadata(post.project_id)
|
||||||
|
form = Draft.persisted_form(post, metadata, "en")
|
||||||
|
|
||||||
|
draft = %{form | "title" => "Renamed Post", "content" => "updated body"}
|
||||||
|
|
||||||
|
assert {:ok, saved} = Persistence.persist(post, draft, "en", metadata, :save)
|
||||||
|
assert saved.title == "Renamed Post"
|
||||||
|
assert saved.status == :draft
|
||||||
|
|
||||||
|
form = Draft.persisted_form(saved, metadata, "en")
|
||||||
|
assert {:ok, published} = Persistence.persist(saved, form, "en", metadata, :publish)
|
||||||
|
assert published.status == :published
|
||||||
|
assert published.file_path != ""
|
||||||
|
end
|
||||||
|
|
||||||
|
test "discard on an unpublished draft is a no-op success", %{post: post} do
|
||||||
|
metadata = Metadata.project_metadata(post.project_id)
|
||||||
|
assert {:ok, _post} = Persistence.discard(post, "en", metadata)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "normalize_params keeps the active language on same-language edits" do
|
||||||
|
params = %{"title" => "T", "language" => "DE "}
|
||||||
|
|
||||||
|
assert Draft.normalize_params(params, "de", "de")["language"] == "de"
|
||||||
|
assert Draft.normalize_params(params, "de", "fr")["language"] == "fr"
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user