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 {
.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 {
display: inline-flex;
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()}
@impl true
def update(%{action: :finish_request}, %{assigns: %{request: nil}} = socket) do
{:ok, socket}
end
def update(%{action: :finish_request, result: result}, socket) do
{:ok, do_finish_request(socket, result)}
end
@@ -252,15 +256,28 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
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
|> assign(:request, nil)
|> build_data()
|> clear_streaming_state()
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
case result do
{:ok, reply} ->
@@ -314,7 +331,7 @@ defmodule BDS.Desktop.ShellLive.ChatEditor do
defp update_request(socket, updater) do
case socket.assigns.request do
nil ->
socket
build_data(socket)
request ->
socket

View File

@@ -12,7 +12,7 @@
<%= unless @chat_editor.needs_api_key? do %>
<span class="chat-model-selector-wrap relative shrink-0">
<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"
phx-click="toggle_chat_model_selector"
phx-target={@myself}
@@ -62,7 +62,7 @@
<h2><%= dgettext("ui", "API Key Required") %></h2>
<p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p>
<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>
<% else %>
@@ -80,18 +80,6 @@
</ul>
</div>
<% 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 %>
<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>
@@ -112,6 +100,18 @@
<% 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 %>
<div class="chat-message assistant streaming flex items-start gap-3" data-testid="chat-streaming-message">
<div class="chat-message-avatar">🤖</div>
@@ -149,12 +149,12 @@
<%= unless @chat_editor.needs_api_key? do %>
<div class="chat-input-container flex shrink-0 flex-col gap-3" data-testid="chat-input-container">
<%= 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 %>
<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>
<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>
<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 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>
<%= 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">
<%= for tab <- @panel_tabs do %>
<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"
phx-click="select_panel_tab"
phx-value-tab={tab}
@@ -467,7 +467,7 @@
<% end %>
</div>
<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"
type="button"
phx-click="toggle_panel"
@@ -531,7 +531,7 @@
><%= @assistant_prompt %></textarea>
<button
class="assistant-sidebar-start-button"
class="assistant-sidebar-start-button ui-button ui-button-primary"
data-testid="assistant-start-button"
type="submit"
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-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",
"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")
]}>
<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">
<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"
phx-click="toggle_media_editor_quick_actions"
phx-target={@myself}
@@ -82,14 +82,14 @@
<% end %>
</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") %>
</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") %>
</button>
<button
class="secondary danger"
class="secondary danger ui-button ui-button-secondary ui-button-danger"
data-testid="media-delete-button"
type="button"
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}>
<div class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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 class="editor-field-row grid gap-4 md:grid-cols-2">
<div class="editor-field flex flex-col gap-1.5">
<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>
<%= if @media_editor.dimensions do %>
<div class="editor-field flex flex-col gap-1.5">
<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>
<% end %>
</div>
<div class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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>
<%= for language <- @media_editor.languages do %>
<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}" %>
</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") %>
</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"]} />
<div class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<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>
</form>
<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 type="button" phx-click="save_media_translation" phx-target={@myself}><%= dgettext("ui", "Save") %></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 class="ui-button ui-button-primary" type="button" phx-click="save_media_translation" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
</div>
</div>
</div>

View File

@@ -6,13 +6,13 @@
</div>
<div class="misc-editor-actions flex flex-wrap items-center justify-end gap-2">
<%= 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 %>
<%= 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 %>
<%= 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 %>
</div>
</div>
@@ -59,7 +59,7 @@
<div class="metadata-diff-tabs" role="tablist">
<%= for tab <- @misc_editor.tabs do %>
<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-entity-tab={tab.id}
type="button"
@@ -69,7 +69,7 @@
>
<span><%= tab.label %></span>
<%= 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 %>
</button>
<% end %>
@@ -95,7 +95,7 @@
<%= if @misc_editor.repair_enabled do %>
<div class="metadata-diff-field-pill-actions">
<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-direction="db_to_file"
data-field={field.field_name}
@@ -109,7 +109,7 @@
</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-direction="file_to_db"
data-field={field.field_name}
@@ -173,7 +173,7 @@
<div class="orphan-files-actions">
<span class="misc-summary-pill"><%= length(@misc_editor.orphan_files) %></span>
<button
class="secondary metadata-diff-action-button"
class="secondary metadata-diff-action-button ui-button ui-button-secondary"
data-testid="metadata-diff-import-button"
type="button"
phx-click="import_metadata_diff_orphans"
@@ -280,8 +280,8 @@
</section>
<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="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="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 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>
@@ -294,7 +294,7 @@
<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>
<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>
<% end %>
</div>
@@ -306,7 +306,7 @@
<% else %>
<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>
<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 %>
<option value={file_path} selected={file_path == @misc_editor.selected_file_path}><%= file_path %></option>
<% end %>

View File

@@ -50,14 +50,14 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
defp render_task_entries(assigns) do
~H"""
<%= 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>
<span><%= dgettext("ui", "No background tasks running") %></span>
</div>
<% else %>
<div class="task-list flex flex-col gap-2">
<%= 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">
<strong><%= task.name %></strong>
<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
~H"""
<%= 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>
<span><%= dgettext("ui", "No shell output yet") %></span>
</div>
@@ -88,6 +88,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<%= for entry <- @output_entries do %>
<div class={[
"panel-entry",
"ui-panel-entry",
"output-entry",
if(Map.get(entry, :level) == "error", do: "output-entry-error")
]}>
@@ -113,17 +114,17 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H"""
<%= 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>
<span><%= dgettext("ui", "No post links yet") %></span>
</div>
<% else %>
<div class="git-log-list flex flex-col gap-2">
<%= 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 %>
<button
class="panel-entry task-entry"
class="panel-entry ui-panel-entry task-entry"
type="button"
phx-click="pin_sidebar_item"
phx-value-route="post"
@@ -138,10 +139,10 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<% end %>
<%= 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 %>
<button
class="panel-entry task-entry"
class="panel-entry ui-panel-entry task-entry"
type="button"
phx-click="pin_sidebar_item"
phx-value-route="post"
@@ -166,7 +167,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
~H"""
<%= if Enum.empty?(@git_entries) do %>
<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>
<span><%= dgettext("ui", "No git history yet") %></span>
</div>
@@ -174,7 +175,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
<% else %>
<div class="git-log-list">
<%= 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>
<span><%= entry.hash %></span>
</div>
@@ -188,7 +189,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
assigns = assign(assigns, :panel_label, ShellData.route_label(tab))
~H"""
<div class="panel-entry">
<div class="panel-entry ui-panel-entry">
<strong><%= @panel_label %></strong>
<span><%= dgettext("ui", "The shared lower panel is available for tasks, output, git details, and editor-specific diagnostics.") %></span>
</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="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-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>
<%= if @post_editor.dirty? do %>
<span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span>
@@ -10,7 +10,7 @@
</div>
<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) %>
</span>
<%= if @post_editor.save_state in [:saving] do %>
@@ -19,7 +19,7 @@
<div class="quick-actions-wrapper relative">
<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"
phx-click="toggle_post_editor_quick_actions"
phx-target={@myself}
@@ -66,17 +66,17 @@
</div>
<%= 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") %>
</button>
<% end %>
<%= 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 %>
</button>
<% end %>
<%= 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") %>
</button>
<% end %>
@@ -115,7 +115,7 @@
<div class="editor-meta flex min-w-0 flex-col gap-4">
<div class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
@@ -164,20 +164,20 @@
<div class="editor-field flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
<label><%= dgettext("ui", "Language") %></label>
<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 %>
<option value={language} selected={language == @post_editor.form["language"]}><%= String.upcase(language) %></option>
<% end %>
</select>
<button
class="secondary compact"
class="secondary compact ui-button ui-button-secondary ui-button-compact"
data-testid="post-detect-language-button"
type="button"
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 flex flex-col gap-1.5">
<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 class="editor-field flex flex-col gap-1.5">
@@ -248,7 +248,7 @@
<%= if @post_editor.show_template_selector? do %>
<div class="editor-field flex flex-col gap-1.5">
<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>
<%= for template <- @post_editor.template_options do %>
<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-field flex flex-col gap-1.5">
<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>

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="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">
<span class={[
"status-badge",
"ui-badge",
"status-#{@script_editor.status}"
]} data-testid="script-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@script_editor.status) %></span>
<%= 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 %>
<button class="secondary scripts-save-button" 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-check-button" 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 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 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 ui-button ui-button-secondary" type="button" phx-click="check_script_editor" phx-target={@myself}><%= dgettext("ui", "Check Syntax") %></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>
<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-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 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", "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", "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 class="ui-input" type="text" name="script_editor[slug]" value={@script_editor.slug} /></div>
</div>
<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", "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", "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 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>
</div>

View File

@@ -10,7 +10,7 @@
<div class="settings-header flex shrink-0 items-center justify-between gap-3">
<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}>
<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>
</div>
@@ -32,29 +32,29 @@
<div class="setting-info">
<label class="setting-label"><%= dgettext("ui", "Project Name") %></label>
</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 class="setting-row">
<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 class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Data Path") %></label></div>
<div class="setting-control">
<div class="setting-input-group">
<input 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>
<input class="ui-input ui-input-readonly" type="text" value={@settings_editor.project_data_path} readonly />
<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 class="setting-row">
<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 class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Main Language") %></label></div>
<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 %>
<option value={language} selected={language == @settings_editor.project["main_language"]}><%= String.upcase(language) %></option>
<% end %>
@@ -76,16 +76,16 @@
</div>
<div class="setting-row">
<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 class="setting-row">
<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 class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Blogmark Category") %></label></div>
<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 %>
<option value={category} selected={category == @settings_editor.project["blogmark_category"]}><%= category %></option>
<% 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>
</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>
<% end %>
@@ -111,7 +111,7 @@
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Default Editor Mode") %></label></div>
<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="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>
@@ -121,7 +121,7 @@
<div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Diff View Style") %></label></div>
<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="side-by-side" selected={@settings_editor.editor["diff_view_style"] == "side-by-side"}><%= dgettext("ui", "Side by Side") %></option>
</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>
</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>
<% end %>
@@ -161,12 +161,12 @@
<tr>
<td><%= category.name %></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><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>
<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>
<%= for template <- @settings_editor.template_options.post do %>
<option value={template.slug} selected={template.slug == category.post_template_slug}><%= template.title %></option>
@@ -174,7 +174,7 @@
</select>
</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>
<%= for template <- @settings_editor.template_options.list do %>
<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}>
<input type="hidden" name="category_settings[category]" value={category.name} />
</form>
<button class="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="submit" form={"category-form-#{category.name}"}><%= dgettext("ui", "Save") %></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>
</td>
</tr>
@@ -198,12 +198,12 @@
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Add Category") %></label></div>
<div class="setting-control">
<div class="setting-input-group">
<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>
<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 ui-button ui-button-primary" type="button" phx-click="add_settings_category" phx-target={@myself}><%= dgettext("ui", "Add") %></button>
</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>
<% end %>
@@ -216,18 +216,18 @@
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Endpoint URL") %></label></div>
<div class="setting-control">
<div class="setting-input-group">
<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>
<input class="ui-input" type="url" name="settings_ai[online_url]" value={@settings_editor.ai["online_url"]} />
<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 class="setting-row">
<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 class="setting-row">
<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 class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Chat Tools") %></label></div>
@@ -239,11 +239,11 @@
</div>
<div class="setting-row">
<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 class="setting-row">
<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 class="setting-row">
<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-control">
<div class="setting-input-group">
<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>
<input class="ui-input" type="url" name="settings_ai[offline_url]" value={@settings_editor.ai["offline_url"]} />
<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 class="setting-row">
<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 class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Airplane Mode") %></label></div>
@@ -268,7 +268,7 @@
</div>
<div class="setting-row">
<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 class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Chat Tools") %></label></div>
@@ -280,11 +280,11 @@
</div>
<div class="setting-row">
<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 class="setting-row">
<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 class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Offline Image Support") %></label></div>
@@ -292,7 +292,7 @@
</div>
<div class="setting-row">
<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>
<datalist id="settings-ai-online-models">
<%= for model <- @settings_editor.online_endpoint_models do %>
@@ -305,7 +305,7 @@
<% end %>
</datalist>
</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>
<% 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>
</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>
<% end %>
@@ -330,12 +330,12 @@
<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>
<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", "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", "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", "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", "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 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 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 class="ui-input" type="text" name="settings_publishing[ssh_remote_path]" value={@settings_editor.publishing["ssh_remote_path"]} /></div></div>
</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>
<% end %>
@@ -350,7 +350,7 @@
<p class="setting-description"><%= agent.config_path || dgettext("ui", "Not supported in the rewrite yet") %></p>
</div>
<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") %>
</button>
</div>
@@ -364,14 +364,14 @@
<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-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" 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" 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" 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" 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="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_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_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_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_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="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="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="open_data_folder"><%= dgettext("ui", "Open Data Folder") %></button>
</div>
</div>
<% end %>

View File

@@ -22,13 +22,13 @@
<div class="style-apply-row">
<label class="style-preview-mode-control">
<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="light" selected={@style_editor.preview_mode == "light"}><%= dgettext("ui", "Light") %></option>
<option value="dark" selected={@style_editor.preview_mode == "dark"}><%= dgettext("ui", "Dark") %></option>
</select>
</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 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-content">
<%= 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>
<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>
<% else %>
<div class="tag-cloud flex flex-wrap gap-2">
@@ -37,25 +37,25 @@
<div class="tags-section-content">
<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">
<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"])} />
<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>
</form>
<%= if @tags_editor.edit_draft != %{} do %>
<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">
<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"])} />
<select name="edit_tag[post_template_slug]">
<select class="ui-input" name="edit_tag[post_template_slug]">
<option value=""><%= dgettext("ui", "No Template") %></option>
<%= for template <- @tags_editor.templates do %>
<option value={template.slug} selected={template.slug == @tags_editor.edit_draft["post_template_slug"]}><%= template.title %></option>
<% end %>
</select>
<button class="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="primary ui-button ui-button-primary" type="button" phx-click="save_tag_editor" phx-target={@myself}><%= dgettext("ui", "Save") %></button>
<button class="danger ui-button ui-button-danger" type="button" phx-click="delete_tag_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div>
</form>
<% end %>
@@ -67,12 +67,12 @@
<div class="tags-section-content">
<div class="merge-form flex flex-col 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 %>
<option value={tag_name} selected={tag_name == @tags_editor.merge_target}><%= tag_name %></option>
<% end %>
</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>
@@ -81,7 +81,7 @@
<div class="tags-section" id="tags-section-sync">
<div class="tags-section-header"><h3><%= dgettext("ui", "Sync") %></h3></div>
<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>

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="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">
<span class={[
"status-badge",
"ui-badge",
"status-#{@template_editor.status}"
]} data-testid="template-status-badge"><%= BDS.Desktop.ShellData.dashboard_status_label(@template_editor.status) %></span>
<%= 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 %>
<button class="secondary templates-save-button" 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 danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></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 ui-button ui-button-secondary" type="button" phx-click="validate_template_editor" phx-target={@myself}><%= dgettext("ui", "Validate") %></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>
<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-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 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", "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", "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 class="ui-input" type="text" name="template_editor[slug]" value={@template_editor.slug} /></div>
</div>
<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>
</div>

View File

@@ -205,15 +205,28 @@ defmodule BDS.Desktop.ShellLiveTest do
settings_editor: %{
selected_section: "project",
search_query: "",
active_sections: [],
project_visible?: false,
active_sections: ["project"],
project_visible?: true,
editor_visible?: false,
content_visible?: false,
ai_visible?: false,
publishing_visible?: false,
data_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
@@ -225,10 +238,10 @@ defmodule BDS.Desktop.ShellLiveTest do
selected_section: "cloud",
tags: [],
new_tag: %{"name" => "", "color" => "#3b82f6"},
edit_draft: %{},
selected: [],
merge_target: nil,
templates: []
edit_draft: %{"name" => "news", "color" => "#3b82f6", "post_template_slug" => ""},
selected: ["news", "updates"],
merge_target: "news",
templates: [%{slug: "post-template", title: "Post Template"}]
}
}
end
@@ -306,6 +319,64 @@ defmodule BDS.Desktop.ShellLiveTest do
assert tags_html =~ "tag-form-row flex flex-wrap items-center gap-3"
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.AI
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")
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
)
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
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(data-testid="script-editor")
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="secondary scripts-save-button")
assert published_script_html =~ ~s(class="secondary scripts-run-button")
assert published_script_html =~ ~s(class="secondary scripts-check-button")
assert published_script_html =~ ~s(class="status-badge ui-badge status-published")
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 ui-button ui-button-secondary")
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 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(data-testid="template-editor")
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="secondary templates-save-button")
assert published_template_html =~ ~s(class="secondary templates-validate-button")
assert published_template_html =~ ~s(class="status-badge ui-badge status-published")
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 ui-button ui-button-secondary")
assert published_template_html =~ "published"
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(class="success")
assert draft_script_html =~ ~s(class="success ui-button ui-button-primary")
draft_script_html =
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(class="success")
assert draft_template_html =~ ~s(class="success ui-button ui-button-primary")
draft_template_html =
view
@@ -3539,7 +3610,7 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(data-testid="chat-model-selector-button")
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-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")
css = desktop_css_source()
@@ -4038,7 +4109,7 @@ defmodule BDS.Desktop.ShellLiveTest do
})
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()
assert css =~ "--chat-input-line-height: 20px;"
@@ -4356,7 +4427,7 @@ defmodule BDS.Desktop.ShellLiveTest do
{user_index, _length} = :binary.match(html, "Newest question")
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, {
:chat_tool_call,

View File

@@ -140,6 +140,20 @@ defmodule BDS.UI.ShellTest do
assert template =~ "data-workbench-session={encoded_workbench_session(@workbench)}"
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
css = css_source()
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 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
)
@@ -519,7 +533,7 @@ defmodule BDS.UI.ShellTest do
assert post_editor_ex =~ "defp build_data(socket)"
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
)