fix: tag editor hopefully working and fixes to test runner

This commit is contained in:
2026-05-02 11:24:51 +02:00
parent a4ea24faa2
commit 73e066c330
5 changed files with 146 additions and 16 deletions

View File

@@ -13,6 +13,8 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
embed_templates("tags_editor_html/*") embed_templates("tags_editor_html/*")
@tags_sections ~w(cloud manage merge)
@spec assign_socket(term()) :: term() @spec assign_socket(term()) :: term()
def assign_socket(socket) do def assign_socket(socket) do
assign(socket, :tags_editor, build(socket.assigns)) assign(socket, :tags_editor, build(socket.assigns))
@@ -198,9 +200,13 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
@spec sync(term(), term(), term()) :: term() @spec sync(term(), term(), term()) :: term()
def sync(socket, reload, append_output) do def sync(socket, reload, append_output) do
_ = append_output case Tags.sync_tags_from_posts(socket.assigns.projects.active_project_id) do
:ok = Tags.sync_tags_json(socket.assigns.projects.active_project_id) {:ok, _tags} -> reload.(socket, socket.assigns.workbench)
reload.(socket, socket.assigns.workbench) {:error, reason} ->
socket
|> append_output.(translated("Tags"), inspect(reason), nil, "error")
|> reload.(socket.assigns.workbench)
end
end end
@spec build(term()) :: term() @spec build(term()) :: term()
@@ -226,6 +232,8 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
select: %{slug: template.slug, title: template.title} select: %{slug: template.slug, title: template.title}
) )
selected_section = current_tags_section(assigns)
%{ %{
tags: tags:
Enum.map(tags, fn tag -> Enum.map(tags, fn tag ->
@@ -235,7 +243,8 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
new_tag: Map.get(assigns, :tags_editor_new_tag, %{"name" => "", "color" => ""}), new_tag: Map.get(assigns, :tags_editor_new_tag, %{"name" => "", "color" => ""}),
edit_draft: edit_draft, edit_draft: edit_draft,
templates: templates, templates: templates,
merge_target: Map.get(assigns, :tags_editor_merge_target, List.first(selected) || "") merge_target: Map.get(assigns, :tags_editor_merge_target, List.first(selected) || ""),
selected_section: selected_section
} }
end end
@@ -293,6 +302,25 @@ defmodule BDS.Desktop.ShellLive.TagsEditor do
end end
end end
defp current_tags_section(assigns) do
assigns
|> current_tab_meta()
|> Map.get(:sidebar_item_id, "tags-cloud")
|> to_string()
|> String.replace_prefix("tags-", "")
|> case do
section when section in @tags_sections -> section
_other -> "cloud"
end
end
defp current_tab_meta(assigns) do
case Map.get(assigns, :current_tab) do
%{type: type, id: id} -> Map.get(assigns[:tab_meta] || %{}, {type, id}, %{})
_other -> %{}
end
end
defp tag_counts(project_id) do defp tag_counts(project_id) do
Repo.all(from post in Post, where: post.project_id == ^project_id, select: post.tags) Repo.all(from post in Post, where: post.project_id == ^project_id, select: post.tags)
|> List.flatten() |> List.flatten()

View File

@@ -1,13 +1,26 @@
<div class="tags-view-shell" data-testid="tags-editor"> <div
id="tags-editor-shell"
class="tags-view-shell"
data-testid="tags-editor"
phx-hook="TagsSectionScroll"
data-selected-tags-section={@tags_editor.selected_section}
data-tags-scroll-target={"tags-section-#{@tags_editor.selected_section}"}
>
<div class="tags-view"> <div class="tags-view">
<div class="tags-view-header"> <div class="tags-view-header">
<h2><%= translated("Tags") %></h2> <h2><%= translated("Tags") %></h2>
</div> </div>
<div class="tags-view-content"> <div class="tags-view-content">
<div class="tags-section"> <div class="tags-section" id="tags-section-cloud">
<div class="tags-section-header"><h3><%= translated("Tag Cloud") %></h3></div> <div class="tags-section-header"><h3><%= translated("Tag Cloud") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<%= if Enum.empty?(@tags_editor.tags) do %>
<div class="tags-empty-state">
<p><%= translated("No tags found") %></p>
<button class="secondary" type="button" phx-click="sync_tags_editor"><%= translated("Discover") %></button>
</div>
<% else %>
<div class="tag-cloud"> <div class="tag-cloud">
<%= for tag <- @tags_editor.tags do %> <%= for tag <- @tags_editor.tags do %>
<button class={["tag-cloud-item", if(tag.name in @tags_editor.selected, do: "selected"), if(tag.color, do: "has-color")]} style={tag_style(tag, @tags_editor.tags)} type="button" phx-click="toggle_tag_selection" phx-value-name={tag.name}> <button class={["tag-cloud-item", if(tag.name in @tags_editor.selected, do: "selected"), if(tag.color, do: "has-color")]} style={tag_style(tag, @tags_editor.tags)} type="button" phx-click="toggle_tag_selection" phx-value-name={tag.name}>
@@ -15,10 +28,11 @@
</button> </button>
<% end %> <% end %>
</div> </div>
<% end %>
</div> </div>
</div> </div>
<div class="tags-section"> <div class="tags-section" id="tags-section-manage">
<div class="tags-section-header"><h3><%= translated("Create / Edit") %></h3></div> <div class="tags-section-header"><h3><%= translated("Create / Edit") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<form class="tag-create-form" phx-change="change_new_tag_editor"> <form class="tag-create-form" phx-change="change_new_tag_editor">
@@ -48,7 +62,7 @@
</div> </div>
</div> </div>
<div class="tags-section"> <div class="tags-section" id="tags-section-merge">
<div class="tags-section-header"><h3><%= translated("Merge Tags") %></h3></div> <div class="tags-section-header"><h3><%= translated("Merge Tags") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<div class="merge-form"> <div class="merge-form">
@@ -64,7 +78,7 @@
</div> </div>
</div> </div>
<div class="tags-section"> <div class="tags-section" id="tags-section-sync">
<div class="tags-section-header"><h3><%= translated("Sync") %></h3></div> <div class="tags-section-header"><h3><%= translated("Sync") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<button class="secondary" type="button" phx-click="sync_tags_editor"><%= translated("Discover") %></button> <button class="secondary" type="button" phx-click="sync_tags_editor"><%= translated("Discover") %></button>

View File

@@ -830,6 +830,35 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}, },
TagsSectionScroll: {
mounted() {
this.lastTargetId = null;
this.scrollToSelectedSection();
},
updated() {
this.scrollToSelectedSection();
},
scrollToSelectedSection() {
const targetId = this.el.dataset.tagsScrollTarget;
if (!targetId || targetId === this.lastTargetId) {
return;
}
this.lastTargetId = targetId;
window.requestAnimationFrame(() => {
const target = document.getElementById(targetId);
if (target && this.el.contains(target)) {
target.scrollIntoView({ block: "start", behavior: "smooth" });
}
});
}
},
ChatSurface: { ChatSurface: {
mounted() { mounted() {
this.stickToBottom = true; this.stickToBottom = true;

View File

@@ -330,6 +330,61 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(data-settings-scroll-target="settings-section-ai") assert html =~ ~s(data-settings-scroll-target="settings-section-ai")
end end
test "tags sidebar selections expose a scroll target for the tags editor" do
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
_html = render_click(view, "select_view", %{"view" => "tags"})
html =
view
|> element("[data-testid='sidebar-open-item'][data-item-id='tags-merge']")
|> render_click()
assert html =~ ~s(data-testid="tags-editor")
assert html =~ ~s(phx-hook="TagsSectionScroll")
assert html =~ ~s(data-selected-tags-section="merge")
assert html =~ ~s(data-tags-scroll-target="tags-section-merge")
end
test "tags discover materializes post tags and enables merge from the tags editor", %{
project: project
} do
assert {:ok, post} =
Posts.create_post(%{
project_id: project.id,
title: "Tagged Post",
content: "Body",
tags: ["Alpha", "Beta"]
})
assert Tags.list_tags(project.id) == []
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
_html = render_click(view, "select_view", %{"view" => "tags"})
_html =
view
|> element("[data-testid='sidebar-open-item'][data-item-id='tags-cloud']")
|> render_click()
html = render_click(view, "sync_tags_editor", %{})
assert Enum.map(Tags.list_tags(project.id), & &1.name) == ["Alpha", "Beta"]
assert html =~ "Alpha"
assert html =~ "Beta"
_html = render_click(view, "toggle_tag_selection", %{"name" => "Alpha"})
_html = render_click(view, "toggle_tag_selection", %{"name" => "Beta"})
_html = render_change(view, "change_merge_target", %{"target" => "Alpha"})
html = render_click(view, "merge_tags_editor", %{})
assert Enum.map(Tags.list_tags(project.id), & &1.name) == ["Alpha"]
assert Repo.get!(Post, post.id).tags == ["Alpha"]
assert html =~ "Alpha"
end
test "database-backed sidebar entries require confirmation before deletion", %{ test "database-backed sidebar entries require confirmation before deletion", %{
project: project, project: project,
temp_dir: temp_dir temp_dir: temp_dir

View File

@@ -2,6 +2,10 @@ cache_root = Path.join(System.tmp_dir!(), "bds-test-cache-#{System.unique_intege
File.mkdir_p!(cache_root) File.mkdir_p!(cache_root)
Application.put_env(:bds, :project_cache_root, cache_root) Application.put_env(:bds, :project_cache_root, cache_root)
Enum.each(["LC_ALL", "LC_MESSAGES", "LANG"], fn variable ->
System.put_env(variable, "en_US.UTF-8")
end)
ExUnit.start() ExUnit.start()
ExUnit.after_suite(fn _results -> ExUnit.after_suite(fn _results ->