feat: phase 4 of tailwind migration

This commit is contained in:
2026-05-04 11:39:31 +02:00
parent 35017f9793
commit 8e715eec8b
15 changed files with 423 additions and 187 deletions

View File

@@ -1,4 +1,135 @@
@layer components { @layer components {
.ui-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 28px;
padding: 4px 10px;
border: 1px solid transparent;
border-radius: 4px;
font: inherit;
line-height: 1.2;
cursor: pointer;
user-select: none;
}
.ui-button:hover:not(:disabled) {
background: var(--vscode-button-hoverBackground, #0e639c);
}
.ui-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.ui-button-primary {
color: var(--vscode-button-foreground, #ffffff);
background: var(--vscode-button-background, var(--vscode-focusBorder));
}
.ui-button-primary:hover:not(:disabled) {
background: var(--vscode-button-hoverBackground, #0e639c);
}
.ui-button-secondary {
color: var(--vscode-button-secondaryForeground, var(--vscode-foreground));
background: var(--vscode-button-secondaryBackground, rgba(255, 255, 255, 0.08));
border-color: var(--vscode-button-border, transparent);
}
.ui-button-secondary:hover:not(:disabled) {
background: var(--vscode-button-secondaryHoverBackground, #4a4d51);
}
.ui-button-danger {
color: var(--vscode-errorForeground, #f48771);
background: transparent;
border-color: color-mix(in srgb, var(--vscode-errorForeground, #f48771) 45%, transparent);
}
.ui-button-danger:hover:not(:disabled) {
background: color-mix(in srgb, var(--vscode-errorForeground, #f48771) 14%, transparent);
}
.ui-button-compact {
min-height: 24px;
padding: 3px 8px;
font-size: 12px;
}
.ui-input,
.ui-textarea {
width: 100%;
padding: 8px 10px;
border: 1px solid var(--vscode-input-border, var(--vscode-panel-border));
border-radius: 4px;
background: var(--vscode-input-background, rgba(255, 255, 255, 0.06));
color: var(--vscode-input-foreground, var(--vscode-foreground));
font: inherit;
}
.ui-textarea {
line-height: 1.5;
resize: vertical;
}
.ui-input:focus,
.ui-textarea:focus {
outline: 1px solid var(--vscode-focusBorder, #007fd4);
outline-offset: 1px;
}
.ui-input-readonly,
.ui-input[readonly] {
opacity: 0.7;
cursor: not-allowed;
}
.ui-input-disabled,
.ui-input:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.ui-tab {
border: none;
color: var(--vscode-tab-inactiveForeground, var(--vscode-foreground));
background: transparent;
}
.ui-tab:hover {
color: var(--vscode-tab-activeForeground, var(--vscode-foreground));
}
.ui-tab-active {
color: var(--vscode-tab-activeForeground, var(--vscode-foreground));
}
.ui-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.ui-panel-entry {
border: 1px solid var(--vscode-panel-border);
border-radius: 4px;
background-color: var(--vscode-sideBar-background);
}
.ui-empty-state {
display: flex;
flex-direction: column;
gap: 6px;
color: var(--vscode-descriptionForeground);
}
.btn-base { .btn-base {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View File

@@ -16,6 +16,10 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
@spec update(map(), Phoenix.LiveView.Socket.t()) :: {:ok, Phoenix.LiveView.Socket.t()} @spec update(map(), Phoenix.LiveView.Socket.t()) :: {:ok, Phoenix.LiveView.Socket.t()}
@impl true @impl true
def update(%{action: :finish_request}, %{assigns: %{request: nil}} = socket) do
{:ok, socket}
end
def update(%{action: :finish_request, result: result}, socket) do def update(%{action: :finish_request, result: result}, socket) do
{:ok, do_finish_request(socket, result)} {:ok, do_finish_request(socket, result)}
end end
@@ -252,15 +256,28 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
notify_parent({:chat_editor_task_cancelled, conversation_id, ref}) notify_parent({:chat_editor_task_cancelled, conversation_id, ref})
# Allow the terminated task's DB connection to be cleaned up before rebuilding.
Process.sleep(20)
socket socket
|> assign(:request, nil) |> assign(:request, nil)
|> build_data() |> clear_streaming_state()
end end
end end
defp clear_streaming_state(socket) do
input = socket.assigns.input || ""
chat_editor = socket.assigns.chat_editor || %{}
chat_editor =
chat_editor
|> Map.put(:is_streaming, false)
|> Map.put(:pending_user_message, nil)
|> Map.put(:streaming_content, "")
|> Map.put(:streaming_tool_markers, [])
|> Map.put(:streaming_inline_surfaces, [])
|> Map.put(:send_disabled?, String.trim(input) == "")
assign(socket, :chat_editor, chat_editor)
end
defp do_finish_request(socket, result) do defp do_finish_request(socket, result) do
case result do case result do
{:ok, reply} -> {:ok, reply} ->
@@ -314,7 +331,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
defp update_request(socket, updater) do defp update_request(socket, updater) do
case socket.assigns.request do case socket.assigns.request do
nil -> nil ->
socket build_data(socket)
request -> request ->
socket socket

View File

@@ -12,7 +12,7 @@
<%= unless @chat_editor.needs_api_key? do %> <%= unless @chat_editor.needs_api_key? do %>
<span class="chat-model-selector-wrap relative shrink-0"> <span class="chat-model-selector-wrap relative shrink-0">
<button <button
class="chat-model-selector-button chat-model-selector-inline inline-flex items-center gap-2" class="chat-model-selector-button chat-model-selector-inline ui-button ui-button-secondary inline-flex items-center gap-2"
type="button" type="button"
phx-click="toggle_chat_model_selector" phx-click="toggle_chat_model_selector"
phx-target={@myself} phx-target={@myself}
@@ -62,7 +62,7 @@
<h2><%= dgettext("ui", "API Key Required") %></h2> <h2><%= dgettext("ui", "API Key Required") %></h2>
<p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p> <p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p>
<div class="api-key-form"> <div class="api-key-form">
<button class="api-key-submit" type="button" phx-click="open_chat_settings" phx-target={@myself}><%= dgettext("ui", "Open Settings") %></button> <button class="api-key-submit ui-button ui-button-primary" type="button" phx-click="open_chat_settings" phx-target={@myself}><%= dgettext("ui", "Open Settings") %></button>
</div> </div>
</div> </div>
<% else %> <% else %>
@@ -80,18 +80,6 @@
</ul> </ul>
</div> </div>
<% else %> <% else %>
<%= if @chat_editor.pending_user_message do %>
<div class="chat-message user pending flex items-start gap-3" data-testid="chat-pending-user-message">
<div class="chat-message-avatar">👤</div>
<div class="chat-message-content">
<div class="chat-message-header">
<span class="chat-message-role"><%= message_role_label(:user) %></span>
</div>
<div class="chat-message-text chat-user-message-text" data-testid="chat-user-message-text"><%= @chat_editor.pending_user_message %></div>
</div>
</div>
<% end %>
<%= for message <- @chat_editor.messages do %> <%= for message <- @chat_editor.messages do %>
<div class={["chat-message flex items-start gap-3", to_string(message.role || "assistant")]}> <div class={["chat-message flex items-start gap-3", to_string(message.role || "assistant")]}>
<div class="chat-message-avatar"><%= if message.role == :user, do: "👤", else: "🤖" %></div> <div class="chat-message-avatar"><%= if message.role == :user, do: "👤", else: "🤖" %></div>
@@ -112,6 +100,18 @@
<% end %> <% end %>
<%= if @chat_editor.pending_user_message do %>
<div class="chat-message user pending flex items-start gap-3" data-testid="chat-pending-user-message">
<div class="chat-message-avatar">👤</div>
<div class="chat-message-content">
<div class="chat-message-header">
<span class="chat-message-role"><%= message_role_label(:user) %></span>
</div>
<div class="chat-message-text chat-user-message-text" data-testid="chat-user-message-text"><%= @chat_editor.pending_user_message %></div>
</div>
</div>
<% end %>
<%= if @chat_editor.is_streaming and (@chat_editor.streaming_content != "" or @chat_editor.streaming_tool_markers != []) do %> <%= if @chat_editor.is_streaming and (@chat_editor.streaming_content != "" or @chat_editor.streaming_tool_markers != []) do %>
<div class="chat-message assistant streaming flex items-start gap-3" data-testid="chat-streaming-message"> <div class="chat-message assistant streaming flex items-start gap-3" data-testid="chat-streaming-message">
<div class="chat-message-avatar">🤖</div> <div class="chat-message-avatar">🤖</div>
@@ -149,12 +149,12 @@
<%= unless @chat_editor.needs_api_key? do %> <%= unless @chat_editor.needs_api_key? do %>
<div class="chat-input-container flex shrink-0 flex-col gap-3" data-testid="chat-input-container"> <div class="chat-input-container flex shrink-0 flex-col gap-3" data-testid="chat-input-container">
<%= if @chat_editor.is_streaming do %> <%= if @chat_editor.is_streaming do %>
<button class="chat-abort-button" data-testid="chat-abort-button" type="button" phx-click="abort_chat_editor_message" phx-target={@myself}>◼ <%= dgettext("ui", "Stop") %></button> <button class="chat-abort-button ui-button ui-button-secondary" data-testid="chat-abort-button" type="button" phx-click="abort_chat_editor_message" phx-target={@myself}>◼ <%= dgettext("ui", "Stop") %></button>
<% end %> <% end %>
<form class="chat-input-wrapper flex items-end gap-2" phx-change="change_chat_editor_input" phx-submit="send_chat_editor_message" phx-target={@myself}> <form class="chat-input-wrapper flex items-end gap-2" phx-change="change_chat_editor_input" phx-submit="send_chat_editor_message" phx-target={@myself}>
<textarea class="chat-input chat-surface-input" name="message" rows="1" placeholder={dgettext("ui", "Type a message...")} disabled={@chat_editor.is_streaming}><%= @chat_editor.input %></textarea> <textarea class="chat-input chat-surface-input ui-textarea" name="message" rows="1" placeholder={dgettext("ui", "Type a message...")} disabled={@chat_editor.is_streaming}><%= @chat_editor.input %></textarea>
<button class="chat-send-button" data-testid="chat-send-button" type="button" phx-click="send_chat_editor_message" phx-target={@myself} disabled={@chat_editor.send_disabled?}>↑</button> <button class="chat-send-button ui-button ui-button-primary" data-testid="chat-send-button" type="button" phx-click="send_chat_editor_message" phx-target={@myself} disabled={@chat_editor.send_disabled?}>↑</button>
</form> </form>
<%= if @chat_editor.action_error do %> <%= if @chat_editor.action_error do %>

View File

@@ -457,7 +457,7 @@
<div class="panel-tabs flex min-w-0 items-center overflow-x-auto"> <div class="panel-tabs flex min-w-0 items-center overflow-x-auto">
<%= for tab <- @panel_tabs do %> <%= for tab <- @panel_tabs do %>
<button <button
class={["panel-tab inline-flex h-9 items-center px-3 text-xs uppercase tracking-wide", if(@workbench.panel.active_tab == tab, do: "active")]} class={["panel-tab", "ui-tab", if(@workbench.panel.active_tab == tab, do: "ui-tab-active"), "inline-flex h-9 items-center px-3 text-xs uppercase tracking-wide", if(@workbench.panel.active_tab == tab, do: "active")]}
type="button" type="button"
phx-click="select_panel_tab" phx-click="select_panel_tab"
phx-value-tab={tab} phx-value-tab={tab}
@@ -467,7 +467,7 @@
<% end %> <% end %>
</div> </div>
<button <button
class="panel-close inline-flex h-8 w-8 items-center justify-center" class="panel-close ui-button ui-button-secondary inline-flex h-8 w-8 items-center justify-center"
data-testid="panel-close" data-testid="panel-close"
type="button" type="button"
phx-click="toggle_panel" phx-click="toggle_panel"
@@ -531,7 +531,7 @@
><%= @assistant_prompt %></textarea> ><%= @assistant_prompt %></textarea>
<button <button
class="assistant-sidebar-start-button" class="assistant-sidebar-start-button ui-button ui-button-primary"
data-testid="assistant-start-button" data-testid="assistant-start-button"
type="submit" type="submit"
disabled={String.trim(@assistant_prompt || "") == ""} disabled={String.trim(@assistant_prompt || "") == ""}

View File

@@ -2,7 +2,7 @@
<div class="editor-header flex shrink-0 items-start justify-between gap-3"> <div class="editor-header flex shrink-0 items-start justify-between gap-3">
<div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"> <div class="editor-tabs flex min-w-0 flex-1 overflow-hidden">
<div class={[ <div class={[
"editor-tab active inline-flex max-w-full items-center gap-2 overflow-hidden px-3 py-2", "editor-tab ui-tab ui-tab-active active inline-flex max-w-full items-center gap-2 overflow-hidden px-3 py-2",
if(@media_editor.dirty?, do: "dirty") if(@media_editor.dirty?, do: "dirty")
]}> ]}>
<span class="editor-tab-title truncate" data-testid="editor-title"><%= @media_editor.display_title %></span> <span class="editor-tab-title truncate" data-testid="editor-title"><%= @media_editor.display_title %></span>
@@ -19,7 +19,7 @@
<div class="quick-actions-wrapper relative"> <div class="quick-actions-wrapper relative">
<button <button
class="secondary quick-actions-btn inline-flex items-center gap-2" class="secondary quick-actions-btn ui-button ui-button-secondary inline-flex items-center gap-2"
type="button" type="button"
phx-click="toggle_media_editor_quick_actions" phx-click="toggle_media_editor_quick_actions"
phx-target={@myself} phx-target={@myself}
@@ -82,14 +82,14 @@
<% end %> <% end %>
</div> </div>
<button class="secondary" type="button" phx-click="replace_media_editor_file" phx-target={@myself}> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="replace_media_editor_file" phx-target={@myself}>
<%= dgettext("ui", "Replace File") %> <%= dgettext("ui", "Replace File") %>
</button> </button>
<button data-testid="media-save-button" type="button" phx-click="save_media_editor" phx-target={@myself}> <button class="ui-button ui-button-primary" data-testid="media-save-button" type="button" phx-click="save_media_editor" phx-target={@myself}>
<%= dgettext("ui", "Save") %> <%= dgettext("ui", "Save") %>
</button> </button>
<button <button
class="secondary danger" class="secondary danger ui-button ui-button-secondary ui-button-danger"
data-testid="media-delete-button" data-testid="media-delete-button"
type="button" type="button"
phx-click="open_overlay" phx-click="open_overlay"
@@ -120,56 +120,56 @@
<form class="media-editor-details-form flex flex-col gap-4" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}> <form class="media-editor-details-form flex flex-col gap-4" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "File Name") %></label> <label><%= dgettext("ui", "File Name") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.original_name} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.original_name} disabled />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "MIME Type") %></label> <label><%= dgettext("ui", "MIME Type") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.mime_type} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.mime_type} disabled />
</div> </div>
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row grid gap-4 md:grid-cols-2">
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Size") %></label> <label><%= dgettext("ui", "Size") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.file_size} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.file_size} disabled />
</div> </div>
<%= if @media_editor.dimensions do %> <%= if @media_editor.dimensions do %>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Dimensions") %></label> <label><%= dgettext("ui", "Dimensions") %></label>
<input class="post-editor-input disabled" type="text" value={@media_editor.dimensions} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.dimensions} disabled />
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input" type="text" name="media_editor[title]" value={@media_editor.form["title"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[title]" value={@media_editor.form["title"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Alt Text") %></label> <label><%= dgettext("ui", "Alt Text") %></label>
<input class="post-editor-input" type="text" name="media_editor[alt]" value={@media_editor.form["alt"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[alt]" value={@media_editor.form["alt"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Caption") %></label> <label><%= dgettext("ui", "Caption") %></label>
<textarea class="post-editor-textarea" name="media_editor[caption]" rows="3"><%= @media_editor.form["caption"] %></textarea> <textarea class="post-editor-textarea ui-textarea" name="media_editor[caption]" rows="3"><%= @media_editor.form["caption"] %></textarea>
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Tags") %></label> <label><%= dgettext("ui", "Tags") %></label>
<input class="post-editor-input" type="text" name="media_editor[tags]" value={@media_editor.form["tags"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[tags]" value={@media_editor.form["tags"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Author") %></label> <label><%= dgettext("ui", "Author") %></label>
<input class="post-editor-input" type="text" name="media_editor[author]" value={@media_editor.form["author"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[author]" value={@media_editor.form["author"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Language") %></label> <label><%= dgettext("ui", "Language") %></label>
<select class="post-editor-input" name="media_editor[language]"> <select class="post-editor-input ui-input" name="media_editor[language]">
<option value=""><%= dgettext("ui", "None") %></option> <option value=""><%= dgettext("ui", "None") %></option>
<%= for language <- @media_editor.languages do %> <%= for language <- @media_editor.languages do %>
<option value={language} selected={language == @media_editor.form["language"]}><%= language_label(language) %></option> <option value={language} selected={language == @media_editor.form["language"]}><%= language_label(language) %></option>
@@ -197,7 +197,7 @@
> >
<%= translation.flag %> <%= language_label(translation.language) %><%= if translation.title, do: " — #{translation.title}" %> <%= translation.flag %> <%= language_label(translation.language) %><%= if translation.title, do: " — #{translation.title}" %>
</button> </button>
<button class="secondary compact" type="button" phx-click="refresh_media_translation" phx-target={@myself} phx-value-language={translation.language}> <button class="secondary compact ui-button ui-button-secondary ui-button-compact" type="button" phx-click="refresh_media_translation" phx-target={@myself} phx-value-language={translation.language}>
<%= dgettext("ui", "Refresh") %> <%= dgettext("ui", "Refresh") %>
</button> </button>
<button class="unlink-btn" type="button" phx-click="delete_media_translation" phx-target={@myself} 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>
@@ -283,20 +283,20 @@
<input type="hidden" name="media_translation[language]" value={@media_editor.editing_translation["language"]} /> <input type="hidden" name="media_translation[language]" value={@media_editor.editing_translation["language"]} />
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input" type="text" name="media_translation[title]" value={@media_editor.editing_translation["title"]} /> <input class="post-editor-input ui-input" type="text" name="media_translation[title]" value={@media_editor.editing_translation["title"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Alt Text") %></label> <label><%= dgettext("ui", "Alt Text") %></label>
<input class="post-editor-input" type="text" name="media_translation[alt]" value={@media_editor.editing_translation["alt"]} /> <input class="post-editor-input ui-input" type="text" name="media_translation[alt]" value={@media_editor.editing_translation["alt"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Caption") %></label> <label><%= dgettext("ui", "Caption") %></label>
<textarea class="post-editor-textarea" name="media_translation[caption]" rows="3"><%= @media_editor.editing_translation["caption"] %></textarea> <textarea class="post-editor-textarea ui-textarea" name="media_translation[caption]" rows="3"><%= @media_editor.editing_translation["caption"] %></textarea>
</div> </div>
</form> </form>
<div class="translation-modal-footer flex items-center justify-end gap-2"> <div class="translation-modal-footer flex items-center justify-end gap-2">
<button class="secondary" type="button" phx-click="close_media_translation_editor" phx-target={@myself}><%= dgettext("ui", "Cancel") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="close_media_translation_editor" phx-target={@myself}><%= dgettext("ui", "Cancel") %></button>
<button type="button" phx-click="save_media_translation" phx-target={@myself}><%= dgettext("ui", "Save") %></button> <button class="ui-button ui-button-primary" type="button" phx-click="save_media_translation" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -6,13 +6,13 @@
</div> </div>
<div class="misc-editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="misc-editor-actions flex flex-wrap items-center justify-end gap-2">
<%= if refreshable?(@misc_editor.kind) do %> <%= if refreshable?(@misc_editor.kind) do %>
<button class="secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself}><%= dgettext("ui", "Refresh") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself}><%= dgettext("ui", "Refresh") %></button>
<% end %> <% end %>
<%= if @misc_editor.kind == :site_validation do %> <%= if @misc_editor.kind == :site_validation do %>
<button class="primary" type="button" phx-click="apply_site_validation" phx-target={@myself} disabled={Enum.empty?(@misc_editor.missing_url_paths) and Enum.empty?(@misc_editor.extra_url_paths) and Enum.empty?(@misc_editor.updated_post_url_paths)}><%= dgettext("ui", "Apply") %></button> <button class="primary ui-button ui-button-primary" type="button" phx-click="apply_site_validation" phx-target={@myself} disabled={Enum.empty?(@misc_editor.missing_url_paths) and Enum.empty?(@misc_editor.extra_url_paths) and Enum.empty?(@misc_editor.updated_post_url_paths)}><%= dgettext("ui", "Apply") %></button>
<% end %> <% end %>
<%= if @misc_editor.kind == :find_duplicates do %> <%= if @misc_editor.kind == :find_duplicates do %>
<button class="secondary" type="button" phx-click="dismiss_selected_duplicates" phx-target={@myself} disabled={MapSet.size(@misc_editor.selected_pairs) == 0}><%= dgettext("ui", "Dismiss Checked") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="dismiss_selected_duplicates" phx-target={@myself} disabled={MapSet.size(@misc_editor.selected_pairs) == 0}><%= dgettext("ui", "Dismiss Checked") %></button>
<% end %> <% end %>
</div> </div>
</div> </div>
@@ -59,7 +59,7 @@
<div class="metadata-diff-tabs" role="tablist"> <div class="metadata-diff-tabs" role="tablist">
<%= for tab <- @misc_editor.tabs do %> <%= for tab <- @misc_editor.tabs do %>
<button <button
class={["metadata-diff-tab", if(@misc_editor.active_tab == tab.id, do: "active")]} class={["metadata-diff-tab", "ui-tab", if(@misc_editor.active_tab == tab.id, do: "active ui-tab-active")]}
data-testid="metadata-diff-tab" data-testid="metadata-diff-tab"
data-entity-tab={tab.id} data-entity-tab={tab.id}
type="button" type="button"
@@ -69,7 +69,7 @@
> >
<span><%= tab.label %></span> <span><%= tab.label %></span>
<%= if tab.badge_count > 0 do %> <%= if tab.badge_count > 0 do %>
<span class="tab-badge"><%= tab.badge_count %></span> <span class="tab-badge ui-badge"><%= tab.badge_count %></span>
<% end %> <% end %>
</button> </button>
<% end %> <% end %>
@@ -95,7 +95,7 @@
<%= if @misc_editor.repair_enabled do %> <%= if @misc_editor.repair_enabled do %>
<div class="metadata-diff-field-pill-actions"> <div class="metadata-diff-field-pill-actions">
<button <button
class="secondary metadata-diff-action-button" class="secondary metadata-diff-action-button ui-button ui-button-secondary"
data-testid="metadata-diff-repair-button" data-testid="metadata-diff-repair-button"
data-direction="db_to_file" data-direction="db_to_file"
data-field={field.field_name} data-field={field.field_name}
@@ -109,7 +109,7 @@
</button> </button>
<button <button
class="secondary metadata-diff-action-button" class="secondary metadata-diff-action-button ui-button ui-button-secondary"
data-testid="metadata-diff-repair-button" data-testid="metadata-diff-repair-button"
data-direction="file_to_db" data-direction="file_to_db"
data-field={field.field_name} data-field={field.field_name}
@@ -173,7 +173,7 @@
<div class="orphan-files-actions"> <div class="orphan-files-actions">
<span class="misc-summary-pill"><%= length(@misc_editor.orphan_files) %></span> <span class="misc-summary-pill"><%= length(@misc_editor.orphan_files) %></span>
<button <button
class="secondary metadata-diff-action-button" class="secondary metadata-diff-action-button ui-button ui-button-secondary"
data-testid="metadata-diff-import-button" data-testid="metadata-diff-import-button"
type="button" type="button"
phx-click="import_metadata_diff_orphans" phx-click="import_metadata_diff_orphans"
@@ -280,8 +280,8 @@
</section> </section>
<div class="translation-validation-actions"> <div class="translation-validation-actions">
<button class="secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself} data-testid="translation-validation-revalidate"><%= dgettext("ui", "translationValidation.revalidate") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="rerun_misc_editor" phx-target={@myself} data-testid="translation-validation-revalidate"><%= dgettext("ui", "translationValidation.revalidate") %></button>
<button class="primary" type="button" phx-click="fix_translation_validation" phx-target={@myself} data-testid="translation-validation-fix" disabled={not @misc_editor.can_fix?}><%= dgettext("ui", "translationValidation.fix") %></button> <button class="primary ui-button ui-button-primary" type="button" phx-click="fix_translation_validation" phx-target={@myself} data-testid="translation-validation-fix" disabled={not @misc_editor.can_fix?}><%= dgettext("ui", "translationValidation.fix") %></button>
</div> </div>
</div> </div>
@@ -294,7 +294,7 @@
<span>→</span> <span>→</span>
<button class="linkish" type="button" phx-click="open_duplicate_post" phx-target={@myself} phx-value-id={BDS.MapUtils.attr(pair, :post_id_b)} phx-value-title={BDS.MapUtils.attr(pair, :title_b)}><%= BDS.MapUtils.attr(pair, :title_b) %></button> <button class="linkish" type="button" phx-click="open_duplicate_post" phx-target={@myself} phx-value-id={BDS.MapUtils.attr(pair, :post_id_b)} phx-value-title={BDS.MapUtils.attr(pair, :title_b)}><%= BDS.MapUtils.attr(pair, :title_b) %></button>
<span class="misc-summary-pill"><%= if(BDS.MapUtils.attr(pair, :exact_match), do: dgettext("ui", "Exact Match"), else: "#{Float.round((BDS.MapUtils.attr(pair, :similarity) || 0.0) * 100, 1)}%") %></span> <span class="misc-summary-pill"><%= if(BDS.MapUtils.attr(pair, :exact_match), do: dgettext("ui", "Exact Match"), else: "#{Float.round((BDS.MapUtils.attr(pair, :similarity) || 0.0) * 100, 1)}%") %></span>
<button class="secondary" type="button" phx-click="dismiss_duplicate_pair" phx-target={@myself} phx-value-post-id-a={BDS.MapUtils.attr(pair, :post_id_a)} phx-value-post-id-b={BDS.MapUtils.attr(pair, :post_id_b)}><%= dgettext("ui", "Dismiss") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="dismiss_duplicate_pair" phx-target={@myself} phx-value-post-id-a={BDS.MapUtils.attr(pair, :post_id_a)} phx-value-post-id-b={BDS.MapUtils.attr(pair, :post_id_b)}><%= dgettext("ui", "Dismiss") %></button>
</article> </article>
<% end %> <% end %>
</div> </div>
@@ -306,7 +306,7 @@
<% else %> <% else %>
<form class="git-diff-toolbar" phx-change="select_git_diff_file" phx-target={@myself}> <form class="git-diff-toolbar" phx-change="select_git_diff_file" phx-target={@myself}>
<label for="git-diff-file-select"><%= dgettext("ui", "gitDiff.changedFiles") %></label> <label for="git-diff-file-select"><%= dgettext("ui", "gitDiff.changedFiles") %></label>
<select id="git-diff-file-select" data-testid="git-diff-file-select" name="path"> <select class="ui-input" id="git-diff-file-select" data-testid="git-diff-file-select" name="path">
<%= for file_path <- @misc_editor.files do %> <%= for file_path <- @misc_editor.files do %>
<option value={file_path} selected={file_path == @misc_editor.selected_file_path}><%= file_path %></option> <option value={file_path} selected={file_path == @misc_editor.selected_file_path}><%= file_path %></option>
<% end %> <% end %>

View File

@@ -50,14 +50,14 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
defp render_task_entries(assigns) do defp render_task_entries(assigns) do
~H""" ~H"""
<%= if Enum.empty?(Map.get(@task_status, :tasks, [])) do %> <%= if Enum.empty?(Map.get(@task_status, :tasks, [])) do %>
<div class="panel-entry panel-empty-state"> <div class="panel-entry ui-panel-entry panel-empty-state ui-empty-state">
<strong><%= dgettext("ui", "Tasks") %></strong> <strong><%= dgettext("ui", "Tasks") %></strong>
<span><%= dgettext("ui", "No background tasks running") %></span> <span><%= dgettext("ui", "No background tasks running") %></span>
</div> </div>
<% else %> <% else %>
<div class="task-list flex flex-col gap-2"> <div class="task-list flex flex-col gap-2">
<%= for task <- Map.get(@task_status, :tasks, []) do %> <%= for task <- Map.get(@task_status, :tasks, []) do %>
<div class="panel-entry task-entry flex flex-col gap-2"> <div class="panel-entry ui-panel-entry task-entry flex flex-col gap-2">
<div class="task-entry-header flex items-center justify-between gap-2"> <div class="task-entry-header flex items-center justify-between gap-2">
<strong><%= task.name %></strong> <strong><%= task.name %></strong>
<span class={"task-status task-status-#{task.status}"}><%= Map.get(task, :status_label, task.status |> to_string() |> String.capitalize()) %></span> <span class={"task-status task-status-#{task.status}"}><%= Map.get(task, :status_label, task.status |> to_string() |> String.capitalize()) %></span>
@@ -79,7 +79,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
defp render_output_entries(assigns) do defp render_output_entries(assigns) do
~H""" ~H"""
<%= if Enum.empty?(@output_entries) do %> <%= if Enum.empty?(@output_entries) do %>
<div class="panel-entry panel-empty-state output-list"> <div class="panel-entry ui-panel-entry panel-empty-state ui-empty-state output-list">
<strong><%= dgettext("ui", "Output") %></strong> <strong><%= dgettext("ui", "Output") %></strong>
<span><%= dgettext("ui", "No shell output yet") %></span> <span><%= dgettext("ui", "No shell output yet") %></span>
</div> </div>
@@ -88,6 +88,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<%= for entry <- @output_entries do %> <%= for entry <- @output_entries do %>
<div class={[ <div class={[
"panel-entry", "panel-entry",
"ui-panel-entry",
"output-entry", "output-entry",
if(Map.get(entry, :level) == "error", do: "output-entry-error") if(Map.get(entry, :level) == "error", do: "output-entry-error")
]}> ]}>
@@ -113,17 +114,17 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H""" ~H"""
<%= if Enum.empty?(@backlinks) and Enum.empty?(@outlinks) do %> <%= if Enum.empty?(@backlinks) and Enum.empty?(@outlinks) do %>
<div class="panel-entry panel-empty-state"> <div class="panel-entry ui-panel-entry panel-empty-state ui-empty-state">
<strong><%= dgettext("ui", "Post Links") %></strong> <strong><%= dgettext("ui", "Post Links") %></strong>
<span><%= dgettext("ui", "No post links yet") %></span> <span><%= dgettext("ui", "No post links yet") %></span>
</div> </div>
<% else %> <% else %>
<div class="git-log-list flex flex-col gap-2"> <div class="git-log-list flex flex-col gap-2">
<%= if Enum.any?(@backlinks) do %> <%= if Enum.any?(@backlinks) do %>
<div class="panel-entry"><strong><%= dgettext("ui", "Backlinks") %></strong></div> <div class="panel-entry ui-panel-entry"><strong><%= dgettext("ui", "Backlinks") %></strong></div>
<%= for entry <- @backlinks do %> <%= for entry <- @backlinks do %>
<button <button
class="panel-entry task-entry" class="panel-entry ui-panel-entry task-entry"
type="button" type="button"
phx-click="pin_sidebar_item" phx-click="pin_sidebar_item"
phx-value-route="post" phx-value-route="post"
@@ -138,10 +139,10 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<% end %> <% end %>
<%= if Enum.any?(@outlinks) do %> <%= if Enum.any?(@outlinks) do %>
<div class="panel-entry"><strong><%= dgettext("ui", "Links To") %></strong></div> <div class="panel-entry ui-panel-entry"><strong><%= dgettext("ui", "Links To") %></strong></div>
<%= for entry <- @outlinks do %> <%= for entry <- @outlinks do %>
<button <button
class="panel-entry task-entry" class="panel-entry ui-panel-entry task-entry"
type="button" type="button"
phx-click="pin_sidebar_item" phx-click="pin_sidebar_item"
phx-value-route="post" phx-value-route="post"
@@ -166,7 +167,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H""" ~H"""
<%= if Enum.empty?(@git_entries) do %> <%= if Enum.empty?(@git_entries) do %>
<div class="git-log-list flex flex-col gap-2"> <div class="git-log-list flex flex-col gap-2">
<div class="panel-entry panel-empty-state"> <div class="panel-entry ui-panel-entry panel-empty-state ui-empty-state">
<strong><%= dgettext("ui", "Git Log") %></strong> <strong><%= dgettext("ui", "Git Log") %></strong>
<span><%= dgettext("ui", "No git history yet") %></span> <span><%= dgettext("ui", "No git history yet") %></span>
</div> </div>
@@ -174,7 +175,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<% else %> <% else %>
<div class="git-log-list"> <div class="git-log-list">
<%= for entry <- @git_entries do %> <%= for entry <- @git_entries do %>
<div class="panel-entry task-entry"> <div class="panel-entry ui-panel-entry task-entry">
<strong><%= short_commit_hash(entry.hash) %> <%= entry.subject || dgettext("ui", "No commit subject") %></strong> <strong><%= short_commit_hash(entry.hash) %> <%= entry.subject || dgettext("ui", "No commit subject") %></strong>
<span><%= entry.hash %></span> <span><%= entry.hash %></span>
</div> </div>
@@ -188,7 +189,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
assigns = assign(assigns, :panel_label, ShellData.route_label(tab)) assigns = assign(assigns, :panel_label, ShellData.route_label(tab))
~H""" ~H"""
<div class="panel-entry"> <div class="panel-entry ui-panel-entry">
<strong><%= @panel_label %></strong> <strong><%= @panel_label %></strong>
<span><%= dgettext("ui", "The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.") %></span> <span><%= dgettext("ui", "The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.") %></span>
</div> </div>

View File

@@ -1,7 +1,7 @@
<div class="post-editor editor flex h-full min-h-0 flex-col" data-testid="post-editor"> <div class="post-editor editor flex h-full min-h-0 flex-col" data-testid="post-editor">
<div class="editor-header flex shrink-0 items-start justify-between gap-3"> <div class="editor-header flex shrink-0 items-start justify-between gap-3">
<div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"> <div class="editor-tabs flex min-w-0 flex-1 overflow-hidden">
<div class={["editor-tab active inline-flex max-w-full items-center gap-2 overflow-hidden px-3 py-2", if(@post_editor.dirty?, do: "dirty")]}> <div class={["editor-tab ui-tab ui-tab-active active inline-flex max-w-full items-center gap-2 overflow-hidden px-3 py-2", if(@post_editor.dirty?, do: "dirty")]}>
<span class="editor-tab-title truncate" data-testid="editor-title"><%= @post_editor.display_title %></span> <span class="editor-tab-title truncate" data-testid="editor-title"><%= @post_editor.display_title %></span>
<%= if @post_editor.dirty? do %> <%= if @post_editor.dirty? do %>
<span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span> <span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span>
@@ -10,7 +10,7 @@
</div> </div>
<div class="editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="editor-actions flex flex-wrap items-center justify-end gap-2">
<span class={["status-badge", "status-#{@post_editor.status}"]} data-testid="post-status-badge"> <span class={["status-badge", "ui-badge", "status-#{@post_editor.status}"]} data-testid="post-status-badge">
<%= post_status_label(@post_editor.status) %> <%= post_status_label(@post_editor.status) %>
</span> </span>
<%= if @post_editor.save_state in [:saving] do %> <%= if @post_editor.save_state in [:saving] do %>
@@ -19,7 +19,7 @@
<div class="quick-actions-wrapper relative"> <div class="quick-actions-wrapper relative">
<button <button
class="secondary quick-actions-btn inline-flex items-center gap-2" class="secondary quick-actions-btn ui-button ui-button-secondary inline-flex items-center gap-2"
type="button" type="button"
phx-click="toggle_post_editor_quick_actions" phx-click="toggle_post_editor_quick_actions"
phx-target={@myself} phx-target={@myself}
@@ -66,17 +66,17 @@
</div> </div>
<%= if @post_editor.can_publish? do %> <%= if @post_editor.can_publish? do %>
<button class="success" data-testid="post-publish-button" type="button" phx-click="publish_post_editor" phx-target={@myself}> <button class="success ui-button ui-button-primary" data-testid="post-publish-button" type="button" phx-click="publish_post_editor" phx-target={@myself}>
<%= dgettext("ui", "Publish") %> <%= dgettext("ui", "Publish") %>
</button> </button>
<% end %> <% end %>
<%= if @post_editor.can_publish? do %> <%= if @post_editor.can_publish? do %>
<button class="secondary danger" data-testid="post-discard-button" type="button" phx-click="discard_post_editor" phx-target={@myself} title={@post_editor.discard_title}> <button class="secondary danger ui-button ui-button-secondary ui-button-danger" data-testid="post-discard-button" type="button" phx-click="discard_post_editor" phx-target={@myself} title={@post_editor.discard_title}>
<%= @post_editor.discard_label %> <%= @post_editor.discard_label %>
</button> </button>
<% end %> <% end %>
<%= if @post_editor.can_delete? do %> <%= if @post_editor.can_delete? do %>
<button class="secondary danger" data-testid="post-delete-button" type="button" phx-click="delete_post_editor" phx-target={@myself}> <button class="secondary danger ui-button ui-button-secondary ui-button-danger" data-testid="post-delete-button" type="button" phx-click="delete_post_editor" phx-target={@myself}>
<%= dgettext("ui", "Delete") %> <%= dgettext("ui", "Delete") %>
</button> </button>
<% end %> <% end %>
@@ -115,7 +115,7 @@
<div class="editor-meta flex min-w-0 flex-col gap-4"> <div class="editor-meta flex min-w-0 flex-col gap-4">
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input" type="text" name="post_editor[title]" value={@post_editor.form["title"]} /> <input class="post-editor-input ui-input" type="text" name="post_editor[title]" value={@post_editor.form["title"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
@@ -164,20 +164,20 @@
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Author") %></label> <label><%= dgettext("ui", "Author") %></label>
<input class="post-editor-input" type="text" name="post_editor[author]" value={@post_editor.form["author"]} /> <input class="post-editor-input ui-input" type="text" name="post_editor[author]" value={@post_editor.form["author"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Language") %></label> <label><%= dgettext("ui", "Language") %></label>
<div class="editor-language-row flex items-center gap-2"> <div class="editor-language-row flex items-center gap-2">
<select class="post-editor-input" name="post_editor[language]"> <select class="post-editor-input ui-input" name="post_editor[language]">
<%= for language <- @post_editor.languages do %> <%= for language <- @post_editor.languages do %>
<option value={language} selected={language == @post_editor.form["language"]}><%= String.upcase(language) %></option> <option value={language} selected={language == @post_editor.form["language"]}><%= String.upcase(language) %></option>
<% end %> <% end %>
</select> </select>
<button <button
class="secondary compact" class="secondary compact ui-button ui-button-secondary ui-button-compact"
data-testid="post-detect-language-button" data-testid="post-detect-language-button"
type="button" type="button"
phx-click="detect_post_editor_language" phx-click="detect_post_editor_language"
@@ -200,7 +200,7 @@
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row grid gap-4 md:grid-cols-2">
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Slug") %></label> <label><%= dgettext("ui", "Slug") %></label>
<input class="post-editor-input is-readonly" type="text" readonly value={@post_editor.slug} /> <input class="post-editor-input ui-input is-readonly ui-input-readonly" type="text" readonly value={@post_editor.slug} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
@@ -248,7 +248,7 @@
<%= if @post_editor.show_template_selector? do %> <%= if @post_editor.show_template_selector? do %>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Template") %></label> <label><%= dgettext("ui", "Template") %></label>
<select class="post-editor-input" name="post_editor[template_slug]"> <select class="post-editor-input ui-input" name="post_editor[template_slug]">
<option value=""><%= dgettext("ui", "Default") %></option> <option value=""><%= dgettext("ui", "Default") %></option>
<%= for template <- @post_editor.template_options do %> <%= for template <- @post_editor.template_options do %>
<option value={template.slug} selected={template.slug == @post_editor.form["template_slug"]}><%= template.title %></option> <option value={template.slug} selected={template.slug == @post_editor.form["template_slug"]}><%= template.title %></option>
@@ -316,7 +316,7 @@
<div class={["editor-excerpt-panel", if(not @post_editor.excerpt_expanded, do: "is-collapsed")]}> <div class={["editor-excerpt-panel", if(not @post_editor.excerpt_expanded, do: "is-collapsed")]}>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Excerpt") %></label> <label><%= dgettext("ui", "Excerpt") %></label>
<textarea class="post-editor-textarea post-editor-excerpt" name="post_editor[excerpt]" rows="4"><%= @post_editor.form["excerpt"] %></textarea> <textarea class="post-editor-textarea post-editor-excerpt ui-textarea" name="post_editor[excerpt]" rows="4"><%= @post_editor.form["excerpt"] %></textarea>
</div> </div>
</div> </div>

View File

@@ -1,30 +1,31 @@
<div class="scripts-view-shell editor flex h-full min-h-0 flex-col" data-testid="script-editor"> <div class="scripts-view-shell editor flex h-full min-h-0 flex-col" data-testid="script-editor">
<div class="editor-header scripts-header flex shrink-0 items-start justify-between gap-3"> <div class="editor-header scripts-header flex shrink-0 items-start justify-between gap-3">
<div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"><div class="editor-tab active inline-flex max-w-full items-center overflow-hidden px-3 py-2"><span class="editor-tab-title truncate"><%= @script_editor.title %></span></div></div> <div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"><div class="editor-tab ui-tab ui-tab-active active inline-flex max-w-full items-center overflow-hidden px-3 py-2"><span class="editor-tab-title truncate"><%= @script_editor.title %></span></div></div>
<div class="editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="editor-actions flex flex-wrap items-center justify-end gap-2">
<span class={[ <span class={[
"status-badge", "status-badge",
"ui-badge",
"status-#{@script_editor.status}" "status-#{@script_editor.status}"
]} data-testid="script-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@script_editor.status) %></span> ]} data-testid="script-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@script_editor.status) %></span>
<%= if @script_editor.can_publish? do %> <%= if @script_editor.can_publish? do %>
<button class="success" data-testid="script-publish-button" type="button" phx-click="publish_script_editor" phx-target={@myself}><%= dgettext("ui", "Publish") %></button> <button class="success ui-button ui-button-primary" data-testid="script-publish-button" type="button" phx-click="publish_script_editor" phx-target={@myself}><%= dgettext("ui", "Publish") %></button>
<% end %> <% end %>
<button class="secondary scripts-save-button" type="button" phx-click="save_script_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button> <button class="secondary scripts-save-button ui-button ui-button-secondary" type="button" phx-click="save_script_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
<button class="secondary scripts-run-button" type="button" phx-click="run_script_editor" phx-target={@myself}><%= dgettext("ui", "Run") %></button> <button class="secondary scripts-run-button ui-button ui-button-secondary" type="button" phx-click="run_script_editor" phx-target={@myself}><%= dgettext("ui", "Run") %></button>
<button class="secondary scripts-check-button" type="button" phx-click="check_script_editor" phx-target={@myself}><%= dgettext("ui", "Check Syntax") %></button> <button class="secondary scripts-check-button ui-button ui-button-secondary" type="button" phx-click="check_script_editor" phx-target={@myself}><%= dgettext("ui", "Check Syntax") %></button>
<button class="secondary danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button> <button class="secondary danger ui-button ui-button-secondary ui-button-danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</div> </div>
<form class="editor-content scripts-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" phx-change="change_script_editor" phx-target={@myself}> <form class="editor-content scripts-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" phx-change="change_script_editor" phx-target={@myself}>
<div class="editor-header-row scripts-meta-row grid gap-4"> <div class="editor-header-row scripts-meta-row grid gap-4">
<div class="editor-meta flex min-w-0 flex-col gap-4"> <div class="editor-meta flex min-w-0 flex-col gap-4">
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row grid gap-4 md:grid-cols-2">
<div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Title") %></label><input type="text" name="script_editor[title]" value={@script_editor.title} /></div> <div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Title") %></label><input class="ui-input" type="text" name="script_editor[title]" value={@script_editor.title} /></div>
<div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="script_editor[slug]" value={@script_editor.slug} /></div> <div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Slug") %></label><input class="ui-input" type="text" name="script_editor[slug]" value={@script_editor.slug} /></div>
</div> </div>
<div class="editor-field-row grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]"> <div class="editor-field-row grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]">
<div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Kind") %></label><select name="script_editor[kind]"><option value="utility" selected={@script_editor.kind == "utility"}>utility</option><option value="macro" selected={@script_editor.kind == "macro"}>macro</option><option value="transform" selected={@script_editor.kind == "transform"}>transform</option></select></div> <div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Kind") %></label><select class="ui-input" name="script_editor[kind]"><option value="utility" selected={@script_editor.kind == "utility"}>utility</option><option value="macro" selected={@script_editor.kind == "macro"}>macro</option><option value="transform" selected={@script_editor.kind == "transform"}>transform</option></select></div>
<div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Entrypoint") %></label><select name="script_editor[entrypoint]"><%= for entrypoint <- @script_editor.entrypoints do %><option value={entrypoint} selected={entrypoint == @script_editor.entrypoint}><%= entrypoint %></option><% end %></select></div> <div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Entrypoint") %></label><select class="ui-input" name="script_editor[entrypoint]"><%= for entrypoint <- @script_editor.entrypoints do %><option value={entrypoint} selected={entrypoint == @script_editor.entrypoint}><%= entrypoint %></option><% end %></select></div>
<div class="editor-field scripts-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div> <div class="editor-field scripts-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div>
</div> </div>
</div> </div>

View File

@@ -10,7 +10,7 @@
<div class="settings-header flex shrink-0 items-center justify-between gap-3"> <div class="settings-header flex shrink-0 items-center justify-between gap-3">
<h2 data-testid="editor-title"><%= dgettext("ui", "Settings") %></h2> <h2 data-testid="editor-title"><%= dgettext("ui", "Settings") %></h2>
<form class="settings-search w-full max-w-xs" phx-change="change_settings_search" phx-target={@myself}> <form class="settings-search w-full max-w-xs" phx-change="change_settings_search" phx-target={@myself}>
<input type="text" name="query" value={@settings_editor.search_query} placeholder={dgettext("ui", "Search settings")} /> <input class="ui-input" type="text" name="query" value={@settings_editor.search_query} placeholder={dgettext("ui", "Search settings")} />
</form> </form>
</div> </div>
@@ -32,29 +32,29 @@
<div class="setting-info"> <div class="setting-info">
<label class="setting-label"><%= dgettext("ui", "Project Name") %></label> <label class="setting-label"><%= dgettext("ui", "Project Name") %></label>
</div> </div>
<div class="setting-control"><input type="text" name="settings_project[name]" value={@settings_editor.project["name"]} /></div> <div class="setting-control"><input class="ui-input" type="text" name="settings_project[name]" value={@settings_editor.project["name"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Description") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Description") %></label></div>
<div class="setting-control"><textarea name="settings_project[description]" rows="3"><%= @settings_editor.project["description"] %></textarea></div> <div class="setting-control"><textarea class="ui-textarea" name="settings_project[description]" rows="3"><%= @settings_editor.project["description"] %></textarea></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Data Path") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Data Path") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="text" value={@settings_editor.project_data_path} readonly /> <input class="ui-input ui-input-readonly" type="text" value={@settings_editor.project_data_path} readonly />
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Public URL") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Public URL") %></label></div>
<div class="setting-control"><input type="url" name="settings_project[public_url]" value={@settings_editor.project["public_url"]} /></div> <div class="setting-control"><input class="ui-input" type="url" name="settings_project[public_url]" value={@settings_editor.project["public_url"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Main Language") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Main Language") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_project[main_language]"> <select class="ui-input" name="settings_project[main_language]">
<%= for language <- @settings_editor.supported_languages do %> <%= for language <- @settings_editor.supported_languages do %>
<option value={language} selected={language == @settings_editor.project["main_language"]}><%= String.upcase(language) %></option> <option value={language} selected={language == @settings_editor.project["main_language"]}><%= String.upcase(language) %></option>
<% end %> <% end %>
@@ -76,16 +76,16 @@
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Default Author") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Default Author") %></label></div>
<div class="setting-control"><input type="text" name="settings_project[default_author]" value={@settings_editor.project["default_author"]} /></div> <div class="setting-control"><input class="ui-input" type="text" name="settings_project[default_author]" value={@settings_editor.project["default_author"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Max Posts Per Page") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Max Posts Per Page") %></label></div>
<div class="setting-control"><input type="number" min="1" max="500" name="settings_project[max_posts_per_page]" value={@settings_editor.project["max_posts_per_page"]} /></div> <div class="setting-control"><input class="ui-input" type="number" min="1" max="500" name="settings_project[max_posts_per_page]" value={@settings_editor.project["max_posts_per_page"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Blogmark Category") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Blogmark Category") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_project[blogmark_category]"> <select class="ui-input" name="settings_project[blogmark_category]">
<%= for category <- Enum.map(@settings_editor.categories, & &1.name) do %> <%= for category <- Enum.map(@settings_editor.categories, & &1.name) do %>
<option value={category} selected={category == @settings_editor.project["blogmark_category"]}><%= category %></option> <option value={category} selected={category == @settings_editor.project["blogmark_category"]}><%= category %></option>
<% end %> <% end %>
@@ -97,7 +97,7 @@
<div class="setting-control"><p class="setting-description"><%= dgettext("ui", "Bookmarklet copy support is wired through the desktop runtime and project public URL.") %></p></div> <div class="setting-control"><p class="setting-description"><%= dgettext("ui", "Bookmarklet copy support is wired through the desktop runtime and project public URL.") %></p></div>
</div> </div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div> <div class="setting-actions"><button class="primary ui-button ui-button-primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div>
</div> </div>
<% end %> <% end %>
@@ -111,7 +111,7 @@
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Default Editor Mode") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Default Editor Mode") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_editor[default_mode]"> <select class="ui-input" name="settings_editor[default_mode]">
<option value="wysiwyg" selected={@settings_editor.editor["default_mode"] == "wysiwyg"}><%= dgettext("ui", "WYSIWYG") %></option> <option value="wysiwyg" selected={@settings_editor.editor["default_mode"] == "wysiwyg"}><%= dgettext("ui", "WYSIWYG") %></option>
<option value="markdown" selected={@settings_editor.editor["default_mode"] == "markdown"}><%= dgettext("ui", "Markdown") %></option> <option value="markdown" selected={@settings_editor.editor["default_mode"] == "markdown"}><%= dgettext("ui", "Markdown") %></option>
<option value="preview" selected={@settings_editor.editor["default_mode"] == "preview"}><%= dgettext("ui", "Preview") %></option> <option value="preview" selected={@settings_editor.editor["default_mode"] == "preview"}><%= dgettext("ui", "Preview") %></option>
@@ -121,7 +121,7 @@
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Diff View Style") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Diff View Style") %></label></div>
<div class="setting-control"> <div class="setting-control">
<select name="settings_editor[diff_view_style]"> <select class="ui-input" name="settings_editor[diff_view_style]">
<option value="inline" selected={@settings_editor.editor["diff_view_style"] == "inline"}><%= dgettext("ui", "Inline") %></option> <option value="inline" selected={@settings_editor.editor["diff_view_style"] == "inline"}><%= dgettext("ui", "Inline") %></option>
<option value="side-by-side" selected={@settings_editor.editor["diff_view_style"] == "side-by-side"}><%= dgettext("ui", "Side by Side") %></option> <option value="side-by-side" selected={@settings_editor.editor["diff_view_style"] == "side-by-side"}><%= dgettext("ui", "Side by Side") %></option>
</select> </select>
@@ -136,7 +136,7 @@
<div class="setting-control"><label><input type="checkbox" name="settings_editor[hide_unchanged_regions]" checked={@settings_editor.editor["hide_unchanged_regions"]} /> <%= dgettext("ui", "Collapse unchanged diff hunks") %></label></div> <div class="setting-control"><label><input type="checkbox" name="settings_editor[hide_unchanged_regions]" checked={@settings_editor.editor["hide_unchanged_regions"]} /> <%= dgettext("ui", "Collapse unchanged diff hunks") %></label></div>
</div> </div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div> <div class="setting-actions"><button class="primary ui-button ui-button-primary" type="button" phx-click="save_settings_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div>
</div> </div>
<% end %> <% end %>
@@ -161,12 +161,12 @@
<tr> <tr>
<td><%= category.name %></td> <td><%= category.name %></td>
<td> <td>
<input type="text" name="category_settings[title]" value={category.title} form={"category-form-#{category.name}"} /> <input class="ui-input" type="text" name="category_settings[title]" value={category.title} form={"category-form-#{category.name}"} />
</td> </td>
<td><input type="checkbox" name="category_settings[render_in_lists]" checked={category.render_in_lists} form={"category-form-#{category.name}"} /></td> <td><input type="checkbox" name="category_settings[render_in_lists]" checked={category.render_in_lists} form={"category-form-#{category.name}"} /></td>
<td><input type="checkbox" name="category_settings[show_title]" checked={category.show_title} form={"category-form-#{category.name}"} /></td> <td><input type="checkbox" name="category_settings[show_title]" checked={category.show_title} form={"category-form-#{category.name}"} /></td>
<td> <td>
<select name="category_settings[post_template_slug]" form={"category-form-#{category.name}"}> <select class="ui-input" name="category_settings[post_template_slug]" form={"category-form-#{category.name}"}>
<option value=""><%= dgettext("ui", "Default") %></option> <option value=""><%= dgettext("ui", "Default") %></option>
<%= for template <- @settings_editor.template_options.post do %> <%= for template <- @settings_editor.template_options.post do %>
<option value={template.slug} selected={template.slug == category.post_template_slug}><%= template.title %></option> <option value={template.slug} selected={template.slug == category.post_template_slug}><%= template.title %></option>
@@ -174,7 +174,7 @@
</select> </select>
</td> </td>
<td> <td>
<select name="category_settings[list_template_slug]" form={"category-form-#{category.name}"}> <select class="ui-input" name="category_settings[list_template_slug]" form={"category-form-#{category.name}"}>
<option value=""><%= dgettext("ui", "Default") %></option> <option value=""><%= dgettext("ui", "Default") %></option>
<%= for template <- @settings_editor.template_options.list do %> <%= for template <- @settings_editor.template_options.list do %>
<option value={template.slug} selected={template.slug == category.list_template_slug}><%= template.title %></option> <option value={template.slug} selected={template.slug == category.list_template_slug}><%= template.title %></option>
@@ -186,8 +186,8 @@
<form id={"category-form-#{category.name}"} phx-submit="save_settings_category" phx-target={@myself}> <form id={"category-form-#{category.name}"} phx-submit="save_settings_category" phx-target={@myself}>
<input type="hidden" name="category_settings[category]" value={category.name} /> <input type="hidden" name="category_settings[category]" value={category.name} />
</form> </form>
<button class="secondary" type="submit" form={"category-form-#{category.name}"}><%= dgettext("ui", "Save") %></button> <button class="secondary ui-button ui-button-secondary" type="submit" form={"category-form-#{category.name}"}><%= dgettext("ui", "Save") %></button>
<button class="secondary" type="button" phx-click="remove_settings_category" phx-target={@myself} phx-value-category={category.name} disabled={category.protected?}><%= dgettext("ui", "Remove") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="remove_settings_category" phx-target={@myself} phx-value-category={category.name} disabled={category.protected?}><%= dgettext("ui", "Remove") %></button>
</div> </div>
</td> </td>
</tr> </tr>
@@ -198,12 +198,12 @@
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Add Category") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Add Category") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="text" value={@settings_editor.new_category} phx-change="change_settings_new_category" phx-target={@myself} name="name" /> <input class="ui-input" type="text" value={@settings_editor.new_category} phx-change="change_settings_new_category" phx-target={@myself} name="name" />
<button class="primary" type="button" phx-click="add_settings_category" phx-target={@myself}><%= dgettext("ui", "Add") %></button> <button class="primary ui-button ui-button-primary" type="button" phx-click="add_settings_category" phx-target={@myself}><%= dgettext("ui", "Add") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-actions"><button class="secondary" type="button" phx-click="reset_settings_categories" phx-target={@myself}><%= dgettext("ui", "Reset to Defaults") %></button></div> <div class="setting-actions"><button class="secondary ui-button ui-button-secondary" type="button" phx-click="reset_settings_categories" phx-target={@myself}><%= dgettext("ui", "Reset to Defaults") %></button></div>
</div> </div>
</div> </div>
<% end %> <% end %>
@@ -216,18 +216,18 @@
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Endpoint URL") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Endpoint URL") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="url" name="settings_ai[online_url]" value={@settings_editor.ai["online_url"]} /> <input class="ui-input" type="url" name="settings_ai[online_url]" value={@settings_editor.ai["online_url"]} />
<button class="secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="online"><%= dgettext("ui", "Refresh Online Models") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="online"><%= dgettext("ui", "Refresh Online Models") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online API Key") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online API Key") %></label></div>
<div class="setting-control"><input type="password" name="settings_ai[online_api_key]" value={@settings_editor.ai["online_api_key"]} /></div> <div class="setting-control"><input class="ui-input" type="password" name="settings_ai[online_api_key]" value={@settings_editor.ai["online_api_key"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_chat_model]" value={@settings_editor.ai["online_chat_model"]} /></div> <div class="setting-control"><input class="ui-input" type="text" list="settings-ai-online-models" name="settings_ai[online_chat_model]" value={@settings_editor.ai["online_chat_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Tools") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Tools") %></label></div>
@@ -239,11 +239,11 @@
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Title Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Title Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_title_model]" value={@settings_editor.ai["online_title_model"]} /></div> <div class="setting-control"><input class="ui-input" type="text" list="settings-ai-online-models" name="settings_ai[online_title_model]" value={@settings_editor.ai["online_title_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Image Analysis Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Image Analysis Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-online-models" name="settings_ai[online_image_analysis_model]" value={@settings_editor.ai["online_image_analysis_model"]} /></div> <div class="setting-control"><input class="ui-input" type="text" list="settings-ai-online-models" name="settings_ai[online_image_analysis_model]" value={@settings_editor.ai["online_image_analysis_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Image Support") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Image Support") %></label></div>
@@ -253,14 +253,14 @@
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Endpoint URL") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Endpoint URL") %></label></div>
<div class="setting-control"> <div class="setting-control">
<div class="setting-input-group"> <div class="setting-input-group">
<input type="url" name="settings_ai[offline_url]" value={@settings_editor.ai["offline_url"]} /> <input class="ui-input" type="url" name="settings_ai[offline_url]" value={@settings_editor.ai["offline_url"]} />
<button class="secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="airplane"><%= dgettext("ui", "Refresh Offline Models") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="refresh_settings_ai_models" phx-target={@myself} phx-value-endpoint="airplane"><%= dgettext("ui", "Refresh Offline Models") %></button>
</div> </div>
</div> </div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline API Key") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline API Key") %></label></div>
<div class="setting-control"><input type="password" name="settings_ai[offline_api_key]" value={@settings_editor.ai["offline_api_key"]} /></div> <div class="setting-control"><input class="ui-input" type="password" name="settings_ai[offline_api_key]" value={@settings_editor.ai["offline_api_key"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Airplane Mode") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Airplane Mode") %></label></div>
@@ -268,7 +268,7 @@
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_chat_model]" value={@settings_editor.ai["offline_chat_model"]} /></div> <div class="setting-control"><input class="ui-input" type="text" list="settings-ai-offline-models" name="settings_ai[offline_chat_model]" value={@settings_editor.ai["offline_chat_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Tools") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Tools") %></label></div>
@@ -280,11 +280,11 @@
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Title Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Title Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_title_model]" value={@settings_editor.ai["offline_title_model"]} /></div> <div class="setting-control"><input class="ui-input" type="text" list="settings-ai-offline-models" name="settings_ai[offline_title_model]" value={@settings_editor.ai["offline_title_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Image Analysis Model") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Image Analysis Model") %></label></div>
<div class="setting-control"><input type="text" list="settings-ai-offline-models" name="settings_ai[offline_image_analysis_model]" value={@settings_editor.ai["offline_image_analysis_model"]} /></div> <div class="setting-control"><input class="ui-input" type="text" list="settings-ai-offline-models" name="settings_ai[offline_image_analysis_model]" value={@settings_editor.ai["offline_image_analysis_model"]} /></div>
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Image Support") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Image Support") %></label></div>
@@ -292,7 +292,7 @@
</div> </div>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "System Prompt") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "System Prompt") %></label></div>
<div class="setting-control"><textarea name="settings_ai[system_prompt]" rows="12"><%= @settings_editor.ai["system_prompt"] %></textarea></div> <div class="setting-control"><textarea class="ui-textarea" name="settings_ai[system_prompt]" rows="12"><%= @settings_editor.ai["system_prompt"] %></textarea></div>
</div> </div>
<datalist id="settings-ai-online-models"> <datalist id="settings-ai-online-models">
<%= for model <- @settings_editor.online_endpoint_models do %> <%= for model <- @settings_editor.online_endpoint_models do %>
@@ -305,7 +305,7 @@
<% end %> <% end %>
</datalist> </datalist>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_ai" phx-target={@myself}><%= dgettext("ui", "Save") %></button><button class="secondary" type="button" phx-click="reset_settings_ai_prompt" phx-target={@myself}><%= dgettext("ui", "Reset to Default") %></button></div> <div class="setting-actions"><button class="primary ui-button ui-button-primary" type="button" phx-click="save_settings_ai" phx-target={@myself}><%= dgettext("ui", "Save") %></button><button class="secondary ui-button ui-button-secondary" type="button" phx-click="reset_settings_ai_prompt" phx-target={@myself}><%= dgettext("ui", "Reset to Default") %></button></div>
</div> </div>
<% end %> <% end %>
@@ -322,7 +322,7 @@
<div class="setting-control"><p class="setting-description"><%= dgettext("ui", "Scripting capabilities are configured at the application layer in the rewrite and do not expose runtime switching here.") %></p></div> <div class="setting-control"><p class="setting-description"><%= dgettext("ui", "Scripting capabilities are configured at the application layer in the rewrite and do not expose runtime switching here.") %></p></div>
</div> </div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div> <div class="setting-actions"><button class="primary ui-button ui-button-primary" type="button" phx-click="save_settings_project" phx-target={@myself}><%= dgettext("ui", "Save") %></button></div>
</div> </div>
<% end %> <% end %>
@@ -330,12 +330,12 @@
<div class="setting-section" id="settings-section-publishing"> <div class="setting-section" id="settings-section-publishing">
<div class="setting-section-header"><h3><%= dgettext("ui", "Publishing") %></h3><p class="setting-section-description"><%= dgettext("ui", "Deployment credentials for upload tasks") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "Publishing") %></h3><p class="setting-section-description"><%= dgettext("ui", "Deployment credentials for upload tasks") %></p></div>
<form class="setting-section-content" phx-change="change_settings_publishing" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_publishing" phx-target={@myself}>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "SSH Mode") %></label></div><div class="setting-control"><select name="settings_publishing[ssh_mode]"><option value="scp" selected={@settings_editor.publishing["ssh_mode"] == "scp"}>scp</option><option value="rsync" selected={@settings_editor.publishing["ssh_mode"] == "rsync"}>rsync</option></select></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "SSH Mode") %></label></div><div class="setting-control"><select class="ui-input" name="settings_publishing[ssh_mode]"><option value="scp" selected={@settings_editor.publishing["ssh_mode"] == "scp"}>scp</option><option value="rsync" selected={@settings_editor.publishing["ssh_mode"] == "rsync"}>rsync</option></select></div></div>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Host") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_host]" value={@settings_editor.publishing["ssh_host"]} /></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Host") %></label></div><div class="setting-control"><input class="ui-input" type="text" name="settings_publishing[ssh_host]" value={@settings_editor.publishing["ssh_host"]} /></div></div>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Username") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_user]" value={@settings_editor.publishing["ssh_user"]} /></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Username") %></label></div><div class="setting-control"><input class="ui-input" type="text" name="settings_publishing[ssh_user]" value={@settings_editor.publishing["ssh_user"]} /></div></div>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Remote Path") %></label></div><div class="setting-control"><input type="text" name="settings_publishing[ssh_remote_path]" value={@settings_editor.publishing["ssh_remote_path"]} /></div></div> <div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Remote Path") %></label></div><div class="setting-control"><input class="ui-input" type="text" name="settings_publishing[ssh_remote_path]" value={@settings_editor.publishing["ssh_remote_path"]} /></div></div>
</form> </form>
<div class="setting-actions"><button class="primary" type="button" phx-click="save_settings_publishing" phx-target={@myself}><%= dgettext("ui", "Save") %></button><button class="secondary" type="button" phx-click="clear_settings_publishing" phx-target={@myself}><%= dgettext("ui", "Clear") %></button></div> <div class="setting-actions"><button class="primary ui-button ui-button-primary" type="button" phx-click="save_settings_publishing" phx-target={@myself}><%= dgettext("ui", "Save") %></button><button class="secondary ui-button ui-button-secondary" type="button" phx-click="clear_settings_publishing" phx-target={@myself}><%= dgettext("ui", "Clear") %></button></div>
</div> </div>
<% end %> <% end %>
@@ -350,7 +350,7 @@
<p class="setting-description"><%= agent.config_path || dgettext("ui", "Not supported in the rewrite yet") %></p> <p class="setting-description"><%= agent.config_path || dgettext("ui", "Not supported in the rewrite yet") %></p>
</div> </div>
<div class="setting-control"> <div class="setting-control">
<button class="secondary" type="button" phx-click="toggle_settings_mcp_agent" phx-target={@myself} phx-value-agent={agent.id} disabled={not agent.supported?}> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="toggle_settings_mcp_agent" phx-target={@myself} phx-value-agent={agent.id} disabled={not agent.supported?}>
<%= if agent.configured?, do: dgettext("ui", "Remove"), else: dgettext("ui", "Add") %> <%= if agent.configured?, do: dgettext("ui", "Remove"), else: dgettext("ui", "Add") %>
</button> </button>
</div> </div>
@@ -364,14 +364,14 @@
<div class="setting-section" id="settings-section-data"> <div class="setting-section" id="settings-section-data">
<div class="setting-section-header"><h3><%= dgettext("ui", "Data Maintenance") %></h3><p class="setting-section-description"><%= dgettext("ui", "Rebuild filesystem-backed records and thumbnails") %></p></div> <div class="setting-section-header"><h3><%= dgettext("ui", "Data Maintenance") %></h3><p class="setting-section-description"><%= dgettext("ui", "Rebuild filesystem-backed records and thumbnails") %></p></div>
<div class="setting-actions"> <div class="setting-actions">
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_posts_from_files"><%= dgettext("ui", "Rebuild Posts From Files") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_posts_from_files"><%= dgettext("ui", "Rebuild Posts From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_media_from_files"><%= dgettext("ui", "Rebuild Media From Files") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_media_from_files"><%= dgettext("ui", "Rebuild Media From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_scripts_from_files"><%= dgettext("ui", "Rebuild Scripts From Files") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_scripts_from_files"><%= dgettext("ui", "Rebuild Scripts From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_templates_from_files"><%= dgettext("ui", "Rebuild Templates From Files") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_templates_from_files"><%= dgettext("ui", "Rebuild Templates From Files") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_post_links"><%= dgettext("ui", "Rebuild Links") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_post_links"><%= dgettext("ui", "Rebuild Links") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="regenerate_missing_thumbnails"><%= dgettext("ui", "Regenerate Missing Thumbnails") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="regenerate_missing_thumbnails"><%= dgettext("ui", "Regenerate Missing Thumbnails") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_embedding_index"><%= dgettext("ui", "Rebuild Embedding Index") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="rebuild_embedding_index"><%= dgettext("ui", "Rebuild Embedding Index") %></button>
<button class="secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open Data Folder") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="settings_shell_command" phx-value-action="open_data_folder"><%= dgettext("ui", "Open Data Folder") %></button>
</div> </div>
</div> </div>
<% end %> <% end %>

View File

@@ -22,13 +22,13 @@
<div class="style-apply-row"> <div class="style-apply-row">
<label class="style-preview-mode-control"> <label class="style-preview-mode-control">
<span><%= dgettext("ui", "Preview Mode") %></span> <span><%= dgettext("ui", "Preview Mode") %></span>
<select phx-change="change_style_preview_mode" phx-target={@myself} name="mode"> <select class="ui-input" phx-change="change_style_preview_mode" phx-target={@myself} name="mode">
<option value="auto" selected={@style_editor.preview_mode == "auto"}><%= dgettext("ui", "Auto") %></option> <option value="auto" selected={@style_editor.preview_mode == "auto"}><%= dgettext("ui", "Auto") %></option>
<option value="light" selected={@style_editor.preview_mode == "light"}><%= dgettext("ui", "Light") %></option> <option value="light" selected={@style_editor.preview_mode == "light"}><%= dgettext("ui", "Light") %></option>
<option value="dark" selected={@style_editor.preview_mode == "dark"}><%= dgettext("ui", "Dark") %></option> <option value="dark" selected={@style_editor.preview_mode == "dark"}><%= dgettext("ui", "Dark") %></option>
</select> </select>
</label> </label>
<button class="primary" type="button" phx-click="apply_style_theme" phx-target={@myself} disabled={@style_editor.selected_theme == @style_editor.applied_theme}><%= dgettext("ui", "Apply Theme") %></button> <button class="primary ui-button ui-button-primary" type="button" phx-click="apply_style_theme" phx-target={@myself} disabled={@style_editor.selected_theme == @style_editor.applied_theme}><%= dgettext("ui", "Apply Theme") %></button>
</div> </div>
<div class="style-preview-container"> <div class="style-preview-container">

View File

@@ -16,9 +16,9 @@
<div class="tags-section-header"><h3><%= dgettext("ui", "Tag Cloud") %></h3></div> <div class="tags-section-header"><h3><%= dgettext("ui", "Tag Cloud") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<%= if Enum.empty?(@tags_editor.tags) do %> <%= if Enum.empty?(@tags_editor.tags) do %>
<div class="tags-empty-state flex flex-col gap-3"> <div class="tags-empty-state ui-empty-state flex flex-col gap-3">
<p><%= dgettext("ui", "No tags found") %></p> <p><%= dgettext("ui", "No tags found") %></p>
<button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= dgettext("ui", "Discover") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= dgettext("ui", "Discover") %></button>
</div> </div>
<% else %> <% else %>
<div class="tag-cloud flex flex-wrap gap-2"> <div class="tag-cloud flex flex-wrap gap-2">
@@ -37,25 +37,25 @@
<div class="tags-section-content"> <div class="tags-section-content">
<form class="tag-create-form" phx-change="change_new_tag_editor" phx-target={@myself}> <form class="tag-create-form" phx-change="change_new_tag_editor" phx-target={@myself}>
<div class="tag-form-row flex flex-wrap items-center gap-3"> <div class="tag-form-row flex flex-wrap items-center gap-3">
<input type="text" name="new_tag[name]" value={@tags_editor.new_tag["name"]} placeholder={dgettext("ui", "Tag name")} /> <input class="ui-input" type="text" name="new_tag[name]" value={@tags_editor.new_tag["name"]} placeholder={dgettext("ui", "Tag name")} />
<input type="color" name="new_tag[color]" value={if(@tags_editor.new_tag["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.new_tag["color"])} /> <input type="color" name="new_tag[color]" value={if(@tags_editor.new_tag["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.new_tag["color"])} />
<button class="primary" type="button" phx-click="create_tag_editor" phx-target={@myself}><%= dgettext("ui", "Create") %></button> <button class="primary ui-button ui-button-primary" type="button" phx-click="create_tag_editor" phx-target={@myself}><%= dgettext("ui", "Create") %></button>
</div> </div>
</form> </form>
<%= if @tags_editor.edit_draft != %{} do %> <%= if @tags_editor.edit_draft != %{} do %>
<form class="tag-edit-form" phx-change="change_edit_tag_editor" phx-target={@myself}> <form class="tag-edit-form" phx-change="change_edit_tag_editor" phx-target={@myself}>
<div class="tag-form-row flex flex-wrap items-center gap-3"> <div class="tag-form-row flex flex-wrap items-center gap-3">
<input type="text" name="edit_tag[name]" value={@tags_editor.edit_draft["name"]} /> <input class="ui-input" type="text" name="edit_tag[name]" value={@tags_editor.edit_draft["name"]} />
<input type="color" name="edit_tag[color]" value={if(@tags_editor.edit_draft["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.edit_draft["color"])} /> <input type="color" name="edit_tag[color]" value={if(@tags_editor.edit_draft["color"] in [nil, ""], do: "#3b82f6", else: @tags_editor.edit_draft["color"])} />
<select name="edit_tag[post_template_slug]"> <select class="ui-input" name="edit_tag[post_template_slug]">
<option value=""><%= dgettext("ui", "No Template") %></option> <option value=""><%= dgettext("ui", "No Template") %></option>
<%= for template <- @tags_editor.templates do %> <%= for template <- @tags_editor.templates do %>
<option value={template.slug} selected={template.slug == @tags_editor.edit_draft["post_template_slug"]}><%= template.title %></option> <option value={template.slug} selected={template.slug == @tags_editor.edit_draft["post_template_slug"]}><%= template.title %></option>
<% end %> <% end %>
</select> </select>
<button class="primary" type="button" phx-click="save_tag_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button> <button class="primary ui-button ui-button-primary" type="button" phx-click="save_tag_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
<button class="danger" type="button" phx-click="delete_tag_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button> <button class="danger ui-button ui-button-danger" type="button" phx-click="delete_tag_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</form> </form>
<% end %> <% end %>
@@ -67,12 +67,12 @@
<div class="tags-section-content"> <div class="tags-section-content">
<div class="merge-form flex flex-col gap-3"> <div class="merge-form flex flex-col gap-3">
<div class="tag-form-row flex flex-wrap items-center gap-3"> <div class="tag-form-row flex flex-wrap items-center gap-3">
<select phx-change="change_merge_target" name="target" phx-target={@myself}> <select class="ui-input" phx-change="change_merge_target" name="target" phx-target={@myself}>
<%= for tag_name <- @tags_editor.selected do %> <%= for tag_name <- @tags_editor.selected do %>
<option value={tag_name} selected={tag_name == @tags_editor.merge_target}><%= tag_name %></option> <option value={tag_name} selected={tag_name == @tags_editor.merge_target}><%= tag_name %></option>
<% end %> <% end %>
</select> </select>
<button class="primary" type="button" phx-click="merge_tags_editor" disabled={length(@tags_editor.selected) < 2} phx-target={@myself}><%= dgettext("ui", "Merge") %></button> <button class="primary ui-button ui-button-primary" type="button" phx-click="merge_tags_editor" disabled={length(@tags_editor.selected) < 2} phx-target={@myself}><%= dgettext("ui", "Merge") %></button>
</div> </div>
</div> </div>
</div> </div>
@@ -81,7 +81,7 @@
<div class="tags-section" id="tags-section-sync"> <div class="tags-section" id="tags-section-sync">
<div class="tags-section-header"><h3><%= dgettext("ui", "Sync") %></h3></div> <div class="tags-section-header"><h3><%= dgettext("ui", "Sync") %></h3></div>
<div class="tags-section-content"> <div class="tags-section-content">
<button class="secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= dgettext("ui", "Discover") %></button> <button class="secondary ui-button ui-button-secondary" type="button" phx-click="sync_tags_editor" phx-target={@myself}><%= dgettext("ui", "Discover") %></button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,28 +1,29 @@
<div class="templates-view-shell editor flex h-full min-h-0 flex-col" data-testid="template-editor"> <div class="templates-view-shell editor flex h-full min-h-0 flex-col" data-testid="template-editor">
<div class="editor-header templates-header flex shrink-0 items-start justify-between gap-3"> <div class="editor-header templates-header flex shrink-0 items-start justify-between gap-3">
<div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"><div class="editor-tab active inline-flex max-w-full items-center overflow-hidden px-3 py-2"><span class="editor-tab-title truncate"><%= @template_editor.title %></span></div></div> <div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"><div class="editor-tab ui-tab ui-tab-active active inline-flex max-w-full items-center overflow-hidden px-3 py-2"><span class="editor-tab-title truncate"><%= @template_editor.title %></span></div></div>
<div class="editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="editor-actions flex flex-wrap items-center justify-end gap-2">
<span class={[ <span class={[
"status-badge", "status-badge",
"ui-badge",
"status-#{@template_editor.status}" "status-#{@template_editor.status}"
]} data-testid="template-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@template_editor.status) %></span> ]} data-testid="template-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@template_editor.status) %></span>
<%= if @template_editor.can_publish? do %> <%= if @template_editor.can_publish? do %>
<button class="success" data-testid="template-publish-button" type="button" phx-click="publish_template_editor" phx-target={@myself}><%= dgettext("ui", "Publish") %></button> <button class="success ui-button ui-button-primary" data-testid="template-publish-button" type="button" phx-click="publish_template_editor" phx-target={@myself}><%= dgettext("ui", "Publish") %></button>
<% end %> <% end %>
<button class="secondary templates-save-button" type="button" phx-click="save_template_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button> <button class="secondary templates-save-button ui-button ui-button-secondary" type="button" phx-click="save_template_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
<button class="secondary templates-validate-button" type="button" phx-click="validate_template_editor" phx-target={@myself}><%= dgettext("ui", "Validate") %></button> <button class="secondary templates-validate-button ui-button ui-button-secondary" type="button" phx-click="validate_template_editor" phx-target={@myself}><%= dgettext("ui", "Validate") %></button>
<button class="secondary danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button> <button class="secondary danger ui-button ui-button-secondary ui-button-danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</div> </div>
<form class="editor-content templates-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" phx-change="change_template_editor" phx-target={@myself}> <form class="editor-content templates-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" phx-change="change_template_editor" phx-target={@myself}>
<div class="editor-header-row templates-meta-row grid gap-4"> <div class="editor-header-row templates-meta-row grid gap-4">
<div class="editor-meta flex min-w-0 flex-col gap-4"> <div class="editor-meta flex min-w-0 flex-col gap-4">
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row grid gap-4 md:grid-cols-2">
<div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Title") %></label><input type="text" name="template_editor[title]" value={@template_editor.title} /></div> <div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Title") %></label><input class="ui-input" type="text" name="template_editor[title]" value={@template_editor.title} /></div>
<div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="template_editor[slug]" value={@template_editor.slug} /></div> <div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Slug") %></label><input class="ui-input" type="text" name="template_editor[slug]" value={@template_editor.slug} /></div>
</div> </div>
<div class="editor-field-row grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]"> <div class="editor-field-row grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]">
<div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Kind") %></label><select name="template_editor[kind]"><option value="post" selected={@template_editor.kind == :post or @template_editor.kind == "post"}>post</option><option value="list" selected={@template_editor.kind == :list or @template_editor.kind == "list"}>list</option><option value="not-found" selected={@template_editor.kind == :"not-found" or @template_editor.kind == "not-found"}>not-found</option><option value="partial" selected={@template_editor.kind == :partial or @template_editor.kind == "partial"}>partial</option></select></div> <div class="editor-field flex flex-col gap-1.5"><label><%= dgettext("ui", "Kind") %></label><select class="ui-input" name="template_editor[kind]"><option value="post" selected={@template_editor.kind == :post or @template_editor.kind == "post"}>post</option><option value="list" selected={@template_editor.kind == :list or @template_editor.kind == "list"}>list</option><option value="not-found" selected={@template_editor.kind == :"not-found" or @template_editor.kind == "not-found"}>not-found</option><option value="partial" selected={@template_editor.kind == :partial or @template_editor.kind == "partial"}>partial</option></select></div>
<div class="editor-field templates-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div> <div class="editor-field templates-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div>
</div> </div>
</div> </div>

View File

@@ -205,15 +205,28 @@ defmodule BDS.Desktop.ShellLiveTest do
settings_editor: %{ settings_editor: %{
selected_section: "project", selected_section: "project",
search_query: "", search_query: "",
active_sections: [], active_sections: ["project"],
project_visible?: false, project_visible?: true,
editor_visible?: false, editor_visible?: false,
content_visible?: false, content_visible?: false,
ai_visible?: false, ai_visible?: false,
publishing_visible?: false, publishing_visible?: false,
data_visible?: false, data_visible?: false,
technology_visible?: false, technology_visible?: false,
mcp_visible?: false mcp_visible?: false,
project: %{
"name" => "Shell Project",
"description" => "Project settings",
"public_url" => "https://example.test",
"main_language" => "en",
"blog_languages" => ["en", "fr"],
"default_author" => "Author",
"max_posts_per_page" => 10,
"blogmark_category" => "notes"
},
project_data_path: "/tmp/shell-project",
supported_languages: ["en", "fr"],
categories: [%{name: "notes"}, %{name: "posts"}]
} }
} }
end end
@@ -225,10 +238,10 @@ defmodule BDS.Desktop.ShellLiveTest do
selected_section: "cloud", selected_section: "cloud",
tags: [], tags: [],
new_tag: %{"name" => "", "color" => "#3b82f6"}, new_tag: %{"name" => "", "color" => "#3b82f6"},
edit_draft: %{}, edit_draft: %{"name" => "news", "color" => "#3b82f6", "post_template_slug" => ""},
selected: [], selected: ["news", "updates"],
merge_target: nil, merge_target: "news",
templates: [] templates: [%{slug: "post-template", title: "Post Template"}]
} }
} }
end end
@@ -306,6 +319,64 @@ defmodule BDS.Desktop.ShellLiveTest do
assert tags_html =~ "tag-form-row flex flex-wrap items-center gap-3" assert tags_html =~ "tag-form-row flex flex-wrap items-center gap-3"
end end
@tag :phase4
test "phase 4 shared primitives render normalized classes" do
conn = Plug.Conn.put_private(build_conn(), :phoenix_endpoint, BDS.Desktop.Endpoint)
{:ok, view, _shell_html} = live_isolated(conn, BDS.Desktop.ShellLive)
post_html = render_component(&BDS.Desktop.ShellLive.PostEditor.render/1, phase3_post_editor_assigns())
media_html = render_component(&BDS.Desktop.ShellLive.MediaEditor.render/1, phase3_media_editor_assigns())
script_html = render_component(&BDS.Desktop.ShellLive.ScriptEditor.render/1, phase3_script_editor_assigns())
template_html = render_component(&BDS.Desktop.ShellLive.TemplateEditor.render/1, phase3_template_editor_assigns())
settings_html = render_component(&BDS.Desktop.ShellLive.SettingsEditor.render/1, phase3_settings_editor_assigns())
tags_html = render_component(&BDS.Desktop.ShellLive.TagsEditor.render/1, phase3_tags_editor_assigns())
panel_html =
render_component(&BDS.Desktop.ShellLive.PanelRenderer.render_panel_body/1, %{
current_tab: %{type: :dashboard, id: "dashboard"},
task_status: %{tasks: []},
output_entries: [],
workbench: %{panel: %{active_tab: :tasks}}
})
assert post_html =~ ~s(class="status-badge ui-badge)
assert post_html =~ ~s(class="success ui-button ui-button-primary)
assert post_html =~ ~s(class="secondary danger ui-button ui-button-secondary ui-button-danger)
assert post_html =~ ~s(class="post-editor-input ui-input)
assert post_html =~ ~s(class="post-editor-textarea post-editor-excerpt ui-textarea)
assert post_html =~ ~s(class="editor-tab ui-tab ui-tab-active)
assert media_html =~ ~s(class="secondary quick-actions-btn ui-button ui-button-secondary)
assert media_html =~ ~s(class="post-editor-input ui-input disabled ui-input-disabled)
assert media_html =~ ~s(class="post-editor-textarea ui-textarea)
assert script_html =~ ~s(class="secondary scripts-save-button ui-button ui-button-secondary)
assert script_html =~ ~s(class="status-badge ui-badge)
assert script_html =~ ~s(class="ui-input")
assert template_html =~ ~s(class="secondary templates-save-button ui-button ui-button-secondary)
assert template_html =~ ~s(class="status-badge ui-badge)
assert template_html =~ ~s(class="ui-input")
assert settings_html =~ ~s(class="ui-input")
assert settings_html =~ ~s(class="primary ui-button ui-button-primary")
assert settings_html =~ ~s(class="secondary ui-button ui-button-secondary")
assert tags_html =~ ~s(class="tags-empty-state ui-empty-state flex flex-col gap-3")
assert tags_html =~ ~s(class="secondary ui-button ui-button-secondary")
assert tags_html =~ ~s(class="primary ui-button ui-button-primary")
assert tags_html =~ ~s(class="danger ui-button ui-button-danger")
assert tags_html =~ ~s(class="ui-input")
shell_html =
view
|> element("[data-testid='toggle-panel']")
|> render_click()
assert shell_html =~ ~s(class="panel-tab ui-tab ui-tab-active)
assert panel_html =~ ~s(class="panel-entry ui-panel-entry panel-empty-state ui-empty-state)
end
alias BDS.Persistence alias BDS.Persistence
alias BDS.AI alias BDS.AI
alias BDS.CliSync.Watcher alias BDS.CliSync.Watcher
@@ -2025,12 +2096,12 @@ defmodule BDS.Desktop.ShellLiveTest do
refute html =~ ~s(class="panel-shell flex min-h-0 shrink-0 flex-col overflow-hidden is-hidden") refute html =~ ~s(class="panel-shell flex min-h-0 shrink-0 flex-col overflow-hidden is-hidden")
assert Regex.match?( assert Regex.match?(
~r/<button class="panel-tab [^"]*active" type="button" phx-click="select_panel_tab" phx-value-tab="tasks">/, ~r/<button class="panel-tab [^"]*ui-tab[^"]*active" type="button" phx-click="select_panel_tab" phx-value-tab="tasks">/,
html html
) )
assert html =~ ~s(class="task-list flex flex-col gap-2") or assert html =~ ~s(class="task-list flex flex-col gap-2") or
html =~ ~s(class="panel-entry panel-empty-state") html =~ ~s(class="panel-entry ui-panel-entry panel-empty-state ui-empty-state")
end end
test "metadata diff tasks localize task text, show progress, and open the diff result in the UI" do test "metadata diff tasks localize task text, show progress, and open the diff result in the UI" do
@@ -3127,10 +3198,10 @@ defmodule BDS.Desktop.ShellLiveTest do
assert published_script_html =~ ~s(class="scripts-view-shell editor flex h-full min-h-0 flex-col") assert published_script_html =~ ~s(class="scripts-view-shell editor flex h-full min-h-0 flex-col")
assert published_script_html =~ ~s(data-testid="script-editor") assert published_script_html =~ ~s(data-testid="script-editor")
assert published_script_html =~ ~s(data-testid="script-status-badge") assert published_script_html =~ ~s(data-testid="script-status-badge")
assert published_script_html =~ ~s(class="status-badge status-published") assert published_script_html =~ ~s(class="status-badge ui-badge status-published")
assert published_script_html =~ ~s(class="secondary scripts-save-button") assert published_script_html =~ ~s(class="secondary scripts-save-button ui-button ui-button-secondary")
assert published_script_html =~ ~s(class="secondary scripts-run-button") assert published_script_html =~ ~s(class="secondary scripts-run-button ui-button ui-button-secondary")
assert published_script_html =~ ~s(class="secondary scripts-check-button") assert published_script_html =~ ~s(class="secondary scripts-check-button ui-button ui-button-secondary")
assert published_script_html =~ "published" assert published_script_html =~ "published"
assert published_script_html =~ "published script" assert published_script_html =~ "published script"
@@ -3148,9 +3219,9 @@ defmodule BDS.Desktop.ShellLiveTest do
assert published_template_html =~ ~s(class="templates-view-shell editor flex h-full min-h-0 flex-col") assert published_template_html =~ ~s(class="templates-view-shell editor flex h-full min-h-0 flex-col")
assert published_template_html =~ ~s(data-testid="template-editor") assert published_template_html =~ ~s(data-testid="template-editor")
assert published_template_html =~ ~s(data-testid="template-status-badge") assert published_template_html =~ ~s(data-testid="template-status-badge")
assert published_template_html =~ ~s(class="status-badge status-published") assert published_template_html =~ ~s(class="status-badge ui-badge status-published")
assert published_template_html =~ ~s(class="secondary templates-save-button") assert published_template_html =~ ~s(class="secondary templates-save-button ui-button ui-button-secondary")
assert published_template_html =~ ~s(class="secondary templates-validate-button") assert published_template_html =~ ~s(class="secondary templates-validate-button ui-button ui-button-secondary")
assert published_template_html =~ "published" assert published_template_html =~ "published"
assert published_template_html =~ "published template" assert published_template_html =~ "published template"
@@ -3166,7 +3237,7 @@ defmodule BDS.Desktop.ShellLiveTest do
}) })
assert draft_script_html =~ ~s(data-testid="script-publish-button") assert draft_script_html =~ ~s(data-testid="script-publish-button")
assert draft_script_html =~ ~s(class="success") assert draft_script_html =~ ~s(class="success ui-button ui-button-primary")
draft_script_html = draft_script_html =
view view
@@ -3187,7 +3258,7 @@ defmodule BDS.Desktop.ShellLiveTest do
}) })
assert draft_template_html =~ ~s(data-testid="template-publish-button") assert draft_template_html =~ ~s(data-testid="template-publish-button")
assert draft_template_html =~ ~s(class="success") assert draft_template_html =~ ~s(class="success ui-button ui-button-primary")
draft_template_html = draft_template_html =
view view
@@ -3539,7 +3610,7 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(data-testid="chat-model-selector-button") assert html =~ ~s(data-testid="chat-model-selector-button")
assert html =~ ~s(class="chat-panel-title-main") assert html =~ ~s(class="chat-panel-title-main")
assert html =~ ~s(class="chat-model-selector-wrap relative shrink-0") assert html =~ ~s(class="chat-model-selector-wrap relative shrink-0")
assert html =~ ~s(class="chat-model-selector-button chat-model-selector-inline inline-flex items-center gap-2") assert html =~ ~s(class="chat-model-selector-button chat-model-selector-inline ui-button ui-button-secondary inline-flex items-center gap-2")
refute html =~ ~s(class="chat-panel-header-actions") refute html =~ ~s(class="chat-panel-header-actions")
css = desktop_css_source() css = desktop_css_source()
@@ -4038,7 +4109,7 @@ defmodule BDS.Desktop.ShellLiveTest do
}) })
assert html =~ ~s(rows="1") assert html =~ ~s(rows="1")
assert html =~ ~s(class="chat-input chat-surface-input") assert html =~ ~s(class="chat-input chat-surface-input ui-textarea")
css = desktop_css_source() css = desktop_css_source()
assert css =~ "--chat-input-line-height: 20px;" assert css =~ "--chat-input-line-height: 20px;"
@@ -4356,7 +4427,7 @@ defmodule BDS.Desktop.ShellLiveTest do
{user_index, _length} = :binary.match(html, "Newest question") {user_index, _length} = :binary.match(html, "Newest question")
assert assistant_index < user_index assert assistant_index < user_index
assert html =~ ~r/<textarea[^>]*class="chat-input chat-surface-input"[^>]*disabled/ assert html =~ ~r/<textarea[^>]*class="chat-input chat-surface-input ui-textarea"[^>]*disabled/
send(view.pid, { send(view.pid, {
:chat_tool_call, :chat_tool_call,

View File

@@ -140,6 +140,20 @@ defmodule BDS.UI.ShellTest do
assert template =~ "data-workbench-session={encoded_workbench_session(@workbench)}" assert template =~ "data-workbench-session={encoded_workbench_session(@workbench)}"
end end
test "phase 4 css defines normalized shared primitives" do
css = css_source()
assert css =~ ".ui-button {"
assert css =~ ".ui-button-secondary {"
assert css =~ ".ui-button-danger {"
assert css =~ ".ui-input,"
assert css =~ ".ui-textarea {"
assert css =~ ".ui-tab {"
assert css =~ ".ui-badge {"
assert css =~ ".ui-panel-entry {"
assert css =~ ".ui-empty-state {"
end
test "tailwind source keeps theme tokens and shared component primitives" do test "tailwind source keeps theme tokens and shared component primitives" do
css = css_source() css = css_source()
app_css = File.read!("/Users/gb/Projects/bDS2/assets/css/app.css") app_css = File.read!("/Users/gb/Projects/bDS2/assets/css/app.css")
@@ -399,7 +413,7 @@ defmodule BDS.UI.ShellTest do
assert css =~ "opacity: 1;" assert css =~ "opacity: 1;"
assert Regex.match?( assert Regex.match?(
~r/class="secondary quick-actions-btn inline-flex items-center gap-2".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= dgettext\("ui", "Quick Actions"\) %><\/span>/s, ~r/class="secondary quick-actions-btn ui-button ui-button-secondary inline-flex items-center gap-2".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= dgettext\("ui", "Quick Actions"\) %><\/span>/s,
template template
) )
@@ -519,7 +533,7 @@ defmodule BDS.UI.ShellTest do
assert post_editor_ex =~ "defp build_data(socket)" assert post_editor_ex =~ "defp build_data(socket)"
assert Regex.match?( assert Regex.match?(
~r/class="secondary quick-actions-btn inline-flex items-center gap-2".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= dgettext\("ui", "Quick Actions"\) %><\/span>/s, ~r/class="secondary quick-actions-btn ui-button ui-button-secondary inline-flex items-center gap-2".*?<span class="quick-actions-btn-icon">⚡<\/span>\s*<span class="quick-actions-btn-label"><%= dgettext\("ui", "Quick Actions"\) %><\/span>/s,
post_template post_template
) )