feat: p hase 3 of tailwind migration
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<div id={"chat-editor-#{@chat_editor.id}"} class="chat-panel" data-testid="chat-editor" phx-hook="ChatSurface">
|
||||
<div class="chat-panel-header">
|
||||
<div class="chat-panel-title">
|
||||
<div id={"chat-editor-#{@chat_editor.id}"} class="chat-panel flex h-full min-h-0 flex-col" data-testid="chat-editor" phx-hook="ChatSurface">
|
||||
<div class="chat-panel-header flex shrink-0 items-center justify-between gap-3">
|
||||
<div class="chat-panel-title flex min-w-0 flex-1 items-center justify-between gap-3">
|
||||
<span class="chat-panel-title-main">
|
||||
<%= if @chat_editor.needs_api_key? do %>
|
||||
<%= dgettext("ui", "AI Chat Setup") %>
|
||||
@@ -10,9 +10,9 @@
|
||||
</span>
|
||||
|
||||
<%= unless @chat_editor.needs_api_key? do %>
|
||||
<span class="chat-model-selector-wrap">
|
||||
<span class="chat-model-selector-wrap relative shrink-0">
|
||||
<button
|
||||
class="chat-model-selector-button chat-model-selector-inline"
|
||||
class="chat-model-selector-button chat-model-selector-inline inline-flex items-center gap-2"
|
||||
type="button"
|
||||
phx-click="toggle_chat_model_selector"
|
||||
phx-target={@myself}
|
||||
@@ -23,7 +23,7 @@
|
||||
</button>
|
||||
|
||||
<%= if @chat_editor.model_selector_open? and @chat_editor.available_models != [] do %>
|
||||
<div class="chat-model-selector-menu">
|
||||
<div class="chat-model-selector-menu absolute right-0 top-full z-10 mt-2 flex min-w-56 flex-col">
|
||||
<%= for group <- @chat_editor.available_model_groups do %>
|
||||
<section class="chat-model-provider-group" data-testid="chat-model-provider-group" data-provider={group.provider}>
|
||||
<%= if length(@chat_editor.available_model_groups) > 1 do %>
|
||||
@@ -33,7 +33,7 @@
|
||||
<%= for model <- group.models do %>
|
||||
<button
|
||||
class={[
|
||||
"chat-model-selector-option",
|
||||
"chat-model-selector-option flex items-center justify-between gap-2 text-left",
|
||||
if(model.id == @chat_editor.effective_model, do: "active")
|
||||
]}
|
||||
type="button"
|
||||
@@ -55,9 +55,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-messages chat-surface-scroll">
|
||||
<div class="chat-messages chat-surface-scroll min-h-0 flex-1 overflow-auto">
|
||||
<%= if @chat_editor.needs_api_key? do %>
|
||||
<div class="chat-welcome chat-api-key-state" data-testid="chat-api-key-required">
|
||||
<div class="chat-welcome chat-api-key-state flex flex-col items-start gap-3" data-testid="chat-api-key-required">
|
||||
<div class="chat-welcome-icon">🔑</div>
|
||||
<h2><%= dgettext("ui", "API Key Required") %></h2>
|
||||
<p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p>
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
<% else %>
|
||||
<%= if Enum.empty?(@chat_editor.messages) and not @chat_editor.is_streaming do %>
|
||||
<div class="chat-welcome">
|
||||
<div class="chat-welcome flex flex-col items-start gap-3">
|
||||
<div class="chat-welcome-icon">🤖</div>
|
||||
<h2><%= dgettext("ui", "Welcome to the AI Assistant") %></h2>
|
||||
<p><%= dgettext("ui", "I can help you manage your blog with rich visualizations. Try asking me to:") %></p>
|
||||
@@ -81,7 +81,7 @@
|
||||
</div>
|
||||
<% else %>
|
||||
<%= if @chat_editor.pending_user_message do %>
|
||||
<div class="chat-message user pending" data-testid="chat-pending-user-message">
|
||||
<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">
|
||||
@@ -93,7 +93,7 @@
|
||||
<% end %>
|
||||
|
||||
<%= for message <- @chat_editor.messages do %>
|
||||
<div class={["chat-message", 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-content">
|
||||
<div class="chat-message-header"><span class="chat-message-role"><%= message_role_label(message.role) %></span></div>
|
||||
@@ -113,7 +113,7 @@
|
||||
<% end %>
|
||||
|
||||
<%= if @chat_editor.is_streaming and (@chat_editor.streaming_content != "" or @chat_editor.streaming_tool_markers != []) do %>
|
||||
<div class="chat-message assistant streaming" 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-content">
|
||||
<div class="chat-message-header">
|
||||
@@ -133,7 +133,7 @@
|
||||
<% end %>
|
||||
|
||||
<%= if @chat_editor.is_streaming and @chat_editor.streaming_content == "" and @chat_editor.streaming_tool_markers == [] do %>
|
||||
<div class="chat-message assistant thinking" data-testid="chat-streaming-thinking">
|
||||
<div class="chat-message assistant thinking flex items-start gap-3" data-testid="chat-streaming-thinking">
|
||||
<div class="chat-message-avatar">🤖</div>
|
||||
<div class="chat-message-content">
|
||||
<div class="chat-thinking-indicator">
|
||||
@@ -147,12 +147,12 @@
|
||||
</div>
|
||||
|
||||
<%= unless @chat_editor.needs_api_key? do %>
|
||||
<div class="chat-input-container" 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 %>
|
||||
<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>
|
||||
<% end %>
|
||||
|
||||
<form class="chat-input-wrapper" 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>
|
||||
<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>
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div
|
||||
class="app"
|
||||
class="app flex h-full w-full flex-col"
|
||||
id="bds-shell-app"
|
||||
phx-hook="AppShell"
|
||||
data-shortcuts={encoded_shortcuts(@client_shortcuts)}
|
||||
@@ -112,12 +112,12 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="app-main">
|
||||
<aside class="activity-bar" data-region="activity-bar">
|
||||
<div class="activity-bar-top">
|
||||
<div class="app-main flex min-h-0 flex-1 overflow-hidden">
|
||||
<aside class="activity-bar flex h-full shrink-0 flex-col items-center justify-between" data-region="activity-bar">
|
||||
<div class="activity-bar-top flex flex-col items-center gap-1">
|
||||
<%= for button <- Enum.filter(@activity_buttons, &(&1.activity_group == :top)) do %>
|
||||
<button
|
||||
class={["activity-bar-item", if(button.active, do: "active")]}
|
||||
class={["activity-bar-item inline-flex h-12 w-12 items-center justify-center", if(button.active, do: "active")]}
|
||||
data-testid="activity-button"
|
||||
data-view={button.id}
|
||||
data-active={to_string(button.active)}
|
||||
@@ -134,10 +134,10 @@
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="activity-bar-bottom">
|
||||
<div class="activity-bar-bottom flex flex-col items-center gap-1">
|
||||
<%= for button <- Enum.filter(@activity_buttons, &(&1.activity_group == :bottom)) do %>
|
||||
<button
|
||||
class={["activity-bar-item", if(button.active, do: "active")]}
|
||||
class={["activity-bar-item inline-flex h-12 w-12 items-center justify-center", if(button.active, do: "active")]}
|
||||
data-testid="activity-button"
|
||||
data-view={button.id}
|
||||
data-active={to_string(button.active)}
|
||||
@@ -157,22 +157,22 @@
|
||||
</aside>
|
||||
|
||||
<section
|
||||
class={["sidebar-shell", if(not @workbench.sidebar_visible, do: "is-hidden")]}
|
||||
class={["sidebar-shell flex min-w-0 shrink-0 overflow-hidden", if(not @workbench.sidebar_visible, do: "is-hidden")]}
|
||||
data-testid="sidebar-shell"
|
||||
style={"width: #{if(@workbench.sidebar_visible, do: @workbench.sidebar_width, else: 0)}px;"}
|
||||
>
|
||||
<div class="sidebar" data-region="sidebar">
|
||||
<div id="sidebar-content" class="sidebar-content sidebar-body" phx-hook="SidebarInteractions">
|
||||
<div class="sidebar flex min-w-0 flex-1 overflow-hidden" data-region="sidebar">
|
||||
<div id="sidebar-content" class="sidebar-content sidebar-body flex min-h-0 flex-1 flex-col overflow-y-auto" phx-hook="SidebarInteractions">
|
||||
<div class="sidebar-section">
|
||||
<% create_action = sidebar_create_action(@workbench.active_view) %>
|
||||
<div class="sidebar-section-header">
|
||||
<div class="sidebar-section-header flex items-center justify-between gap-2">
|
||||
<span><%= String.upcase(sidebar_header_label(@sidebar_header)) %></span>
|
||||
<%= if create_action || ShellSidebarComponents.filters_enabled?(@sidebar_data) do %>
|
||||
<div class="sidebar-actions">
|
||||
<div class="sidebar-actions flex items-center gap-1">
|
||||
<%= if ShellSidebarComponents.filters_enabled?(@sidebar_data) do %>
|
||||
<button
|
||||
class={[
|
||||
"sidebar-action",
|
||||
"sidebar-action inline-flex h-8 w-8 items-center justify-center",
|
||||
if(ShellSidebarComponents.filters_visible?(@sidebar_data), do: "active")
|
||||
]}
|
||||
data-testid="sidebar-filter-toggle"
|
||||
@@ -188,7 +188,7 @@
|
||||
<% end %>
|
||||
<%= if create_action do %>
|
||||
<button
|
||||
class="sidebar-action"
|
||||
class="sidebar-action inline-flex h-8 w-8 items-center justify-center"
|
||||
data-testid="sidebar-create-action"
|
||||
data-sidebar-action={create_action.kind}
|
||||
type="button"
|
||||
@@ -212,16 +212,16 @@
|
||||
<div class="resizable-panel-divider sidebar-divider" data-resize="sidebar" data-role="resize-handle"></div>
|
||||
</section>
|
||||
|
||||
<main class="app-content" data-region="content">
|
||||
<div class="tab-bar" data-region="tab-bar">
|
||||
<main class="app-content flex min-w-0 flex-1 flex-col overflow-hidden" data-region="content">
|
||||
<div class="tab-bar flex h-[35px] shrink-0 items-center overflow-hidden" data-region="tab-bar">
|
||||
<%= if Enum.empty?(@workbench.tabs) do %>
|
||||
<div class="tab-bar-empty"><%= dgettext("ui", "Dashboard") %></div>
|
||||
<div class="tab-bar-empty flex h-full items-center px-3 text-sm"><%= dgettext("ui", "Dashboard") %></div>
|
||||
<% else %>
|
||||
<div class="tab-bar-tabs">
|
||||
<div class="tab-bar-tabs flex min-w-0 flex-1 items-stretch overflow-x-auto">
|
||||
<%= for tab <- @workbench.tabs do %>
|
||||
<div
|
||||
class={[
|
||||
"tab",
|
||||
"tab flex min-w-0 max-w-[240px] shrink-0 items-stretch",
|
||||
if(@workbench.active_tab == {tab.type, tab.id}, do: "active"),
|
||||
if(tab.is_transient, do: "transient"),
|
||||
if(Workbench.dirty?(@workbench, tab.type, tab.id), do: "dirty")
|
||||
@@ -231,21 +231,21 @@
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
class="tab-select"
|
||||
class="tab-select flex min-w-0 flex-1 items-center gap-2 overflow-hidden px-3 text-sm"
|
||||
type="button"
|
||||
phx-click="select_tab"
|
||||
phx-value-type={tab.type}
|
||||
phx-value-id={tab.id}
|
||||
>
|
||||
<span class="tab-icon"><%= raw(ShellData.activity_icon(BDS.Desktop.ShellLive.TabHelpers.tab_icon_id(tab))) %></span>
|
||||
<span class="tab-title"><%= BDS.Desktop.ShellLive.TabHelpers.tab_title(tab, @tab_meta) %></span>
|
||||
<span class="tab-icon shrink-0"><%= raw(ShellData.activity_icon(BDS.Desktop.ShellLive.TabHelpers.tab_icon_id(tab))) %></span>
|
||||
<span class="tab-title truncate"><%= BDS.Desktop.ShellLive.TabHelpers.tab_title(tab, @tab_meta) %></span>
|
||||
</button>
|
||||
<div class="tab-actions">
|
||||
<div class="tab-actions flex items-center gap-1 pr-2">
|
||||
<%= if Workbench.dirty?(@workbench, tab.type, tab.id) do %>
|
||||
<span class="tab-dirty-indicator">●</span>
|
||||
<% end %>
|
||||
<button
|
||||
class="tab-close"
|
||||
class="tab-close inline-flex h-6 w-6 items-center justify-center"
|
||||
data-testid="tab-close"
|
||||
data-tab-type={tab.type}
|
||||
data-tab-id={tab.id}
|
||||
@@ -265,7 +265,7 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<section class="editor-shell" data-region="editor">
|
||||
<section class="editor-shell flex min-h-0 flex-1 flex-col overflow-hidden" data-region="editor">
|
||||
<%= if is_nil(@current_tab) do %>
|
||||
<div class="editor-empty">
|
||||
<div class="dashboard-content">
|
||||
@@ -452,12 +452,12 @@
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<section class={["panel-shell", if(not @workbench.panel.visible, do: "is-hidden")]} data-region="panel">
|
||||
<div class="panel-header">
|
||||
<div class="panel-tabs">
|
||||
<section class={["panel-shell flex min-h-0 shrink-0 flex-col overflow-hidden", if(not @workbench.panel.visible, do: "is-hidden")]} data-region="panel">
|
||||
<div class="panel-header flex items-center justify-between gap-2">
|
||||
<div class="panel-tabs flex min-w-0 items-center overflow-x-auto">
|
||||
<%= for tab <- @panel_tabs do %>
|
||||
<button
|
||||
class={["panel-tab", if(@workbench.panel.active_tab == tab, do: "active")]}
|
||||
class={["panel-tab 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"
|
||||
class="panel-close inline-flex h-8 w-8 items-center justify-center"
|
||||
data-testid="panel-close"
|
||||
type="button"
|
||||
phx-click="toggle_panel"
|
||||
@@ -477,21 +477,21 @@
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div class="panel-content min-h-0 flex-1 overflow-auto">
|
||||
<%= BDS.Desktop.ShellLive.PanelRenderer.render_panel_body(assigns) %>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<section
|
||||
class={["assistant-sidebar-shell", if(not @workbench.assistant_sidebar_visible, do: "is-hidden")]}
|
||||
class={["assistant-sidebar-shell flex min-w-0 shrink-0 overflow-hidden", if(not @workbench.assistant_sidebar_visible, do: "is-hidden")]}
|
||||
data-testid="assistant-shell"
|
||||
style={"width: #{if(@workbench.assistant_sidebar_visible, do: @workbench.assistant_sidebar_width, else: 0)}px;"}
|
||||
>
|
||||
<div class="resizable-panel-divider assistant-divider" data-resize="assistant" data-role="resize-handle"></div>
|
||||
<aside class="assistant-sidebar" data-region="assistant-sidebar">
|
||||
<div class="assistant-content">
|
||||
<header class="assistant-sidebar-header">
|
||||
<aside class="assistant-sidebar flex min-w-0 flex-1 overflow-hidden" data-region="assistant-sidebar">
|
||||
<div class="assistant-content flex min-h-0 flex-1 flex-col">
|
||||
<header class="assistant-sidebar-header flex items-start justify-between gap-3">
|
||||
<div class="assistant-sidebar-heading">
|
||||
<strong><%= dgettext("ui", "AI Assistant") %></strong>
|
||||
<span class="assistant-sidebar-description"><%= dgettext("ui", "AI conversations") %></span>
|
||||
@@ -504,12 +504,12 @@
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<section class="assistant-sidebar-context" data-testid="assistant-context">
|
||||
<div class="assistant-sidebar-context-row">
|
||||
<section class="assistant-sidebar-context flex shrink-0 flex-col gap-2" data-testid="assistant-context">
|
||||
<div class="assistant-sidebar-context-row flex items-center justify-between gap-2">
|
||||
<span class="assistant-sidebar-context-label"><%= dgettext("ui", "Project") %></span>
|
||||
<span class="assistant-sidebar-context-value"><%= BDS.Desktop.ShellLive.ChatSurface.assistant_project_name(@current_project) %></span>
|
||||
</div>
|
||||
<div class="assistant-sidebar-context-row">
|
||||
<div class="assistant-sidebar-context-row flex items-center justify-between gap-2">
|
||||
<span class="assistant-sidebar-context-label"><%= dgettext("ui", "Editor") %></span>
|
||||
<span class="assistant-sidebar-context-value"><%= BDS.Desktop.ShellLive.TabHelpers.tab_title(@current_tab, @tab_meta) %></span>
|
||||
</div>
|
||||
@@ -517,13 +517,13 @@
|
||||
</section>
|
||||
|
||||
<form
|
||||
class="assistant-sidebar-prompt-form"
|
||||
class="assistant-sidebar-prompt-form flex shrink-0 flex-col gap-3"
|
||||
data-testid="assistant-prompt-form"
|
||||
phx-change="update_assistant_prompt"
|
||||
phx-submit="submit_assistant_prompt"
|
||||
>
|
||||
<textarea
|
||||
class="assistant-sidebar-prompt"
|
||||
class="assistant-sidebar-prompt min-h-[8rem] w-full resize-y"
|
||||
data-testid="assistant-prompt-input"
|
||||
name="assistant[prompt]"
|
||||
rows="6"
|
||||
@@ -541,19 +541,19 @@
|
||||
</form>
|
||||
|
||||
<%= if Enum.empty?(@assistant_messages) do %>
|
||||
<div class="assistant-sidebar-welcome">
|
||||
<div class="assistant-sidebar-welcome min-h-0 flex-1 overflow-auto">
|
||||
<%= for card <- @assistant_cards do %>
|
||||
<section class="assistant-card">
|
||||
<section class="assistant-card flex flex-col gap-1">
|
||||
<strong><%= card.label %></strong>
|
||||
<span><%= card.text %></span>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="assistant-sidebar-transcript">
|
||||
<div class="assistant-sidebar-transcript min-h-0 flex-1 overflow-auto">
|
||||
<%= for message <- @assistant_messages do %>
|
||||
<article
|
||||
class={["assistant-sidebar-message", message.role]}
|
||||
class={["assistant-sidebar-message flex flex-col gap-1", message.role]}
|
||||
data-testid={BDS.Desktop.ShellLive.ChatSurface.assistant_message_testid(message.role)}
|
||||
>
|
||||
<span class="assistant-sidebar-message-role"><%= BDS.Desktop.ShellLive.ChatSurface.assistant_message_label(message.role) %></span>
|
||||
@@ -567,12 +567,12 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<footer class="status-bar" data-region="status-bar" data-testid="status-bar">
|
||||
<div class="status-bar-left">
|
||||
<footer class="status-bar flex h-[22px] shrink-0 items-center justify-between gap-2" data-region="status-bar" data-testid="status-bar">
|
||||
<div class="status-bar-left flex min-w-0 items-center gap-2 overflow-hidden">
|
||||
<%= if @is_mac_ui do %>
|
||||
<div class="status-shell-controls" data-testid="status-shell-controls">
|
||||
<div class="status-shell-controls flex items-center gap-1" data-testid="status-shell-controls">
|
||||
<button
|
||||
class="status-shell-toggle-button"
|
||||
class="status-shell-toggle-button inline-flex h-6 w-6 items-center justify-center"
|
||||
data-testid="toggle-sidebar"
|
||||
type="button"
|
||||
phx-click="toggle_sidebar"
|
||||
@@ -584,7 +584,7 @@
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="status-shell-toggle-button"
|
||||
class="status-shell-toggle-button inline-flex h-6 w-6 items-center justify-center"
|
||||
data-testid="toggle-panel"
|
||||
type="button"
|
||||
phx-click="toggle_panel"
|
||||
@@ -596,7 +596,7 @@
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="status-shell-toggle-button"
|
||||
class="status-shell-toggle-button inline-flex h-6 w-6 items-center justify-center"
|
||||
data-testid="toggle-assistant"
|
||||
type="button"
|
||||
phx-click="toggle_assistant_sidebar"
|
||||
@@ -609,9 +609,9 @@
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="project-selector">
|
||||
<div class="project-selector relative shrink-0">
|
||||
<button
|
||||
class="project-selector-trigger"
|
||||
class="project-selector-trigger inline-flex items-center gap-2"
|
||||
data-testid="project-selector-trigger"
|
||||
type="button"
|
||||
title={dgettext("ui", "Switch project")}
|
||||
@@ -659,7 +659,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<button class="status-bar-item status-bar-task-button" data-testid="status-task-button" type="button" phx-click="open_tasks_panel">
|
||||
<button class="status-bar-item status-bar-task-button inline-flex items-center gap-2" data-testid="status-task-button" type="button" phx-click="open_tasks_panel">
|
||||
<%= if @status.left.running_task_message do %>
|
||||
<span class="task-spinner"></span>
|
||||
<% end %>
|
||||
@@ -669,12 +669,12 @@
|
||||
<% end %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-bar-right">
|
||||
<div class="status-bar-right flex items-center gap-2 overflow-hidden">
|
||||
<span class="status-bar-item"><%= @status.right.post_count %></span>
|
||||
<span class="status-bar-item"><%= @status.right.media_count %></span>
|
||||
<span class="status-bar-item theme-badge"><%= @status.right.theme_badge %></span>
|
||||
<button class={["status-bar-item", "offline-badge", if(@status.right.offline_mode, do: "active")]} data-testid="status-offline-button" type="button" phx-click="toggle_offline_mode" title={dgettext("ui", "Toggle offline mode")}>✈</button>
|
||||
<form class="status-bar-item language-badge" data-testid="status-language-form" phx-change="change_ui_language">
|
||||
<form class="status-bar-item language-badge flex items-center gap-1" data-testid="status-language-form" phx-change="change_ui_language">
|
||||
<span><%= dgettext("ui", "UI") %></span>
|
||||
<select class="status-bar-language-select" name="ui_language" data-testid="status-language-select">
|
||||
<%= for language <- @supported_ui_languages do %>
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
<div class="media-editor editor" data-testid="media-editor">
|
||||
<div class="editor-header">
|
||||
<div class="editor-tabs">
|
||||
<div class="media-editor editor flex h-full min-h-0 flex-col" data-testid="media-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",
|
||||
"editor-tab 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" data-testid="editor-title"><%= @media_editor.display_title %></span>
|
||||
<span class="editor-tab-title truncate" data-testid="editor-title"><%= @media_editor.display_title %></span>
|
||||
<%= if @media_editor.dirty? do %>
|
||||
<span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-actions">
|
||||
<div class="editor-actions flex flex-wrap items-center justify-end gap-2">
|
||||
<%= if @media_editor.save_state in [:dirty, :saved] do %>
|
||||
<span class="auto-save-indicator"><%= media_editor_save_state_label(@media_editor.save_state) %></span>
|
||||
<% end %>
|
||||
|
||||
<div class="quick-actions-wrapper">
|
||||
<div class="quick-actions-wrapper relative">
|
||||
<button
|
||||
class="secondary quick-actions-btn"
|
||||
class="secondary quick-actions-btn inline-flex items-center gap-2"
|
||||
type="button"
|
||||
phx-click="toggle_media_editor_quick_actions"
|
||||
phx-target={@myself}
|
||||
@@ -30,16 +29,16 @@
|
||||
</button>
|
||||
|
||||
<%= if @media_editor.quick_actions_open? do %>
|
||||
<div class="quick-actions-menu">
|
||||
<div class="quick-actions-menu absolute right-0 top-full z-10 mt-2 flex min-w-72 flex-col">
|
||||
<%= if @media_editor.is_image do %>
|
||||
<button
|
||||
class="quick-action-item"
|
||||
class="quick-action-item flex items-start gap-3 text-left"
|
||||
data-testid="editor-toolbar-overlay-button"
|
||||
type="button"
|
||||
phx-click="open_overlay"
|
||||
phx-value-kind="ai_suggestions"
|
||||
>
|
||||
<span class="quick-action-text">
|
||||
<span class="quick-action-text flex min-w-0 flex-1 flex-col">
|
||||
<strong><%= dgettext("ui", "AI Suggestions") %></strong>
|
||||
<small><%= dgettext("ui", "Review title, alt text, and caption suggestions") %></small>
|
||||
</span>
|
||||
@@ -50,13 +49,13 @@
|
||||
<% end %>
|
||||
|
||||
<button
|
||||
class="quick-action-item"
|
||||
class="quick-action-item flex items-start gap-3 text-left"
|
||||
type="button"
|
||||
phx-click="detect_media_editor_language"
|
||||
phx-target={@myself}
|
||||
disabled={not @media_editor.can_detect_language?}
|
||||
>
|
||||
<span class="quick-action-text">
|
||||
<span class="quick-action-text flex min-w-0 flex-1 flex-col">
|
||||
<strong><%= dgettext("ui", "Detect Language") %></strong>
|
||||
<small><%= dgettext("ui", "Persist the detected language for this media item") %></small>
|
||||
</span>
|
||||
@@ -66,14 +65,14 @@
|
||||
<div class="quick-actions-divider"></div>
|
||||
|
||||
<button
|
||||
class="quick-action-item"
|
||||
class="quick-action-item flex items-start gap-3 text-left"
|
||||
data-testid="editor-toolbar-overlay-button"
|
||||
type="button"
|
||||
phx-click="open_overlay"
|
||||
phx-value-kind="language_picker"
|
||||
disabled={not @media_editor.can_translate?}
|
||||
>
|
||||
<span class="quick-action-text">
|
||||
<span class="quick-action-text flex min-w-0 flex-1 flex-col">
|
||||
<strong><%= dgettext("ui", "Translate") %></strong>
|
||||
<small><%= dgettext("ui", "Select a target language for this media item") %></small>
|
||||
</span>
|
||||
@@ -101,14 +100,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-content media-editor">
|
||||
<div class="media-preview">
|
||||
<div class="editor-content media-editor grid min-h-0 flex-1 gap-4 overflow-auto xl:grid-cols-[minmax(320px,1fr)_minmax(0,1.2fr)]">
|
||||
<div class="media-preview flex min-h-[16rem] items-center justify-center">
|
||||
<%= if @media_editor.is_image and @media_editor.preview_url do %>
|
||||
<div class="media-preview-image">
|
||||
<img src={@media_editor.preview_url} alt={@media_editor.form["alt"] || @media_editor.original_name} />
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="media-preview-placeholder">
|
||||
<div class="media-preview-placeholder flex h-full w-full flex-col items-center justify-center gap-3">
|
||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="currentColor" opacity="0.3">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"></path>
|
||||
</svg>
|
||||
@@ -117,58 +116,58 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="media-details">
|
||||
<form class="media-editor-details-form" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}>
|
||||
<div class="editor-field">
|
||||
<div class="media-details min-w-0">
|
||||
<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 />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<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 />
|
||||
</div>
|
||||
|
||||
<div class="editor-field-row">
|
||||
<div class="editor-field">
|
||||
<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 />
|
||||
</div>
|
||||
|
||||
<%= if @media_editor.dimensions do %>
|
||||
<div class="editor-field">
|
||||
<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 />
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<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"]} />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<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"]} />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<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"]} />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<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"]} />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<div class="editor-field flex flex-col gap-1.5">
|
||||
<label><%= dgettext("ui", "Language") %></label>
|
||||
<select class="post-editor-input" name="media_editor[language]">
|
||||
<option value=""><%= dgettext("ui", "None") %></option>
|
||||
@@ -180,15 +179,15 @@
|
||||
</form>
|
||||
|
||||
<%= if @media_editor.form["language"] not in [nil, ""] do %>
|
||||
<div class="editor-field media-translations-section">
|
||||
<div class="editor-field media-translations-section flex flex-col gap-2">
|
||||
<label><%= dgettext("ui", "Translations") %></label>
|
||||
|
||||
<%= if Enum.empty?(@media_editor.translations) do %>
|
||||
<div class="no-linked-posts"><%= dgettext("ui", "No translations") %></div>
|
||||
<% else %>
|
||||
<div class="linked-posts-list">
|
||||
<div class="linked-posts-list flex flex-col gap-2">
|
||||
<%= for translation <- @media_editor.translations do %>
|
||||
<div class="linked-post-item">
|
||||
<div class="linked-post-item flex items-center justify-between gap-2">
|
||||
<button
|
||||
class="linked-post-title linked-post-link"
|
||||
type="button"
|
||||
@@ -209,7 +208,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="editor-field linked-posts-section">
|
||||
<div class="editor-field linked-posts-section flex flex-col gap-2">
|
||||
<label>
|
||||
<%= dgettext("ui", "Linked Posts") %>
|
||||
<button class="add-link-btn" type="button" phx-click="toggle_media_post_picker" phx-target={@myself}>
|
||||
@@ -218,7 +217,7 @@
|
||||
</label>
|
||||
|
||||
<%= if @media_editor.post_picker_open? do %>
|
||||
<div class="post-picker">
|
||||
<div class="post-picker flex flex-col gap-3">
|
||||
<div class="post-picker-search">
|
||||
<input
|
||||
type="text"
|
||||
@@ -233,7 +232,7 @@
|
||||
<%= if Enum.empty?(@media_editor.post_picker_results) do %>
|
||||
<div class="no-posts"><%= dgettext("ui", "No posts to link") %></div>
|
||||
<% else %>
|
||||
<div class="post-picker-list">
|
||||
<div class="post-picker-list flex flex-col gap-2">
|
||||
<%= for result <- @media_editor.post_picker_results do %>
|
||||
<button class="post-picker-item" type="button" phx-click="link_media_to_post" phx-target={@myself} phx-value-post-id={result.post_id}>
|
||||
<%= result.title %>
|
||||
@@ -250,9 +249,9 @@
|
||||
<%= if Enum.empty?(@media_editor.linked_posts) do %>
|
||||
<div class="no-linked-posts"><%= dgettext("ui", "Not linked to any posts") %></div>
|
||||
<% else %>
|
||||
<div class="linked-posts-list">
|
||||
<div class="linked-posts-list flex flex-col gap-2">
|
||||
<%= for linked_post <- @media_editor.linked_posts do %>
|
||||
<div class="linked-post-item">
|
||||
<div class="linked-post-item flex items-center justify-between gap-2">
|
||||
<button
|
||||
class="linked-post-title linked-post-link"
|
||||
type="button"
|
||||
@@ -275,27 +274,27 @@
|
||||
|
||||
<%= if @media_editor.editing_translation do %>
|
||||
<div class="translation-modal-backdrop">
|
||||
<div class="translation-modal">
|
||||
<div class="translation-modal-header">
|
||||
<div class="translation-modal flex max-h-[80vh] w-full max-w-2xl flex-col overflow-hidden">
|
||||
<div class="translation-modal-header flex items-center justify-between gap-3">
|
||||
<h2><%= dgettext("ui", "Edit Translation") %></h2>
|
||||
<button class="translation-modal-close" type="button" phx-click="close_media_translation_editor" phx-target={@myself}>×</button>
|
||||
</div>
|
||||
<form class="translation-modal-body" phx-change="change_media_translation" phx-target={@myself}>
|
||||
<form class="translation-modal-body flex flex-col gap-4 overflow-auto" phx-change="change_media_translation" phx-target={@myself}>
|
||||
<input type="hidden" name="media_translation[language]" value={@media_editor.editing_translation["language"]} />
|
||||
<div class="editor-field">
|
||||
<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"]} />
|
||||
</div>
|
||||
<div class="editor-field">
|
||||
<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"]} />
|
||||
</div>
|
||||
<div class="editor-field">
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
<div class="translation-modal-footer">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
<div class="menu-editor-view" data-testid="menu-editor" phx-window-keydown={if(@menu_editor.draft, do: "menu_editor_keydown")} phx-target={@myself}>
|
||||
<div class="menu-editor-header">
|
||||
<div class="menu-editor-view flex h-full min-h-0 flex-col" data-testid="menu-editor" phx-window-keydown={if(@menu_editor.draft, do: "menu_editor_keydown")} phx-target={@myself}>
|
||||
<div class="menu-editor-header flex shrink-0 items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2><%= @menu_editor.title %></h2>
|
||||
<p><%= @menu_editor.description %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="menu-editor-main">
|
||||
<div class="menu-editor-tree-wrap">
|
||||
<div class="menu-editor-toolbar" data-testid="menu-editor-toolbar" role="toolbar" aria-label={@menu_editor.title}>
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="add-entry" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-entry" phx-target={@myself} title={dgettext("ui", "menuEditor.addEntry")}>
|
||||
<div class="menu-editor-main min-h-0 flex-1 overflow-hidden">
|
||||
<div class="menu-editor-tree-wrap flex h-full min-h-0 flex-col">
|
||||
<div class="menu-editor-toolbar flex flex-wrap items-center gap-2" data-testid="menu-editor-toolbar" role="toolbar" aria-label={@menu_editor.title}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="add-entry" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-entry" phx-target={@myself} title={dgettext("ui", "menuEditor.addEntry")}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7 2h2v5h5v2H9v5H7V9H2V7h5V2z" /></svg>
|
||||
</button>
|
||||
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="save" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="save" phx-target={@myself} title={dgettext("ui", "menuEditor.save")}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="save" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="save" phx-target={@myself} title={dgettext("ui", "menuEditor.save")}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h9l3 3v9H2V2zm2 1v3h6V3H4zm0 9h8V7H4v5z" /></svg>
|
||||
</button>
|
||||
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="add-category-archive" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-category-archive" phx-target={@myself} title={dgettext("ui", "menuEditor.addCategoryArchive")}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="add-category-archive" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="add-category-archive" phx-target={@myself} title={dgettext("ui", "menuEditor.addCategoryArchive")}>
|
||||
<span aria-hidden="true"><%= dgettext("ui", "menuEditor.addCategoryArchiveShort") %></span>
|
||||
</button>
|
||||
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="move-up" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-up" phx-target={@myself} title={dgettext("ui", "menuEditor.moveUp")} disabled={not @menu_editor.can_move_up?}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="move-up" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-up" phx-target={@myself} title={dgettext("ui", "menuEditor.moveUp")} disabled={not @menu_editor.can_move_up?}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 3l4 4H9v6H7V7H4l4-4z" /></svg>
|
||||
</button>
|
||||
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="move-down" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-down" phx-target={@myself} title={dgettext("ui", "menuEditor.moveDown")} disabled={not @menu_editor.can_move_down?}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="move-down" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="move-down" phx-target={@myself} title={dgettext("ui", "menuEditor.moveDown")} disabled={not @menu_editor.can_move_down?}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7 3h2v6h3l-4 4-4-4h3V3z" /></svg>
|
||||
</button>
|
||||
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="indent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="indent" phx-target={@myself} title={dgettext("ui", "menuEditor.indent")} disabled={not @menu_editor.can_indent?}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="indent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="indent" phx-target={@myself} title={dgettext("ui", "menuEditor.indent")} disabled={not @menu_editor.can_indent?}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 4h8v2H2V4zm0 3h4v2H2V7zm0 3h8v2H2v-2zm6-1 3 2-3 2V9z" /></svg>
|
||||
</button>
|
||||
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="unindent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="unindent" phx-target={@myself} title={dgettext("ui", "menuEditor.unindent")} disabled={not @menu_editor.can_unindent?}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="unindent" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="unindent" phx-target={@myself} title={dgettext("ui", "menuEditor.unindent")} disabled={not @menu_editor.can_unindent?}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 4h8v2H2V4zm0 3h4v2H2V7zm0 3h8v2H2v-2zm3-1-3 2 3 2V9z" /></svg>
|
||||
</button>
|
||||
|
||||
<button class="menu-editor-tool" data-testid="menu-editor-toolbar-button" data-action="delete" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="delete" phx-target={@myself} title={dgettext("ui", "menuEditor.delete")} disabled={not @menu_editor.can_delete?}>
|
||||
<button class="menu-editor-tool inline-flex h-9 min-w-9 items-center justify-center" data-testid="menu-editor-toolbar-button" data-action="delete" type="button" phx-click="menu_editor_toolbar_action" phx-value-action="delete" phx-target={@myself} title={dgettext("ui", "menuEditor.delete")} disabled={not @menu_editor.can_delete?}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M6 2h4l1 1h3v2H2V3h3l1-1zm-1 4h2v6H5V6zm4 0h2v6H9V6z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<%= if @menu_editor.items == [] do %>
|
||||
<div class="menu-editor-empty"><%= dgettext("ui", "menuEditor.empty") %></div>
|
||||
<div class="menu-editor-empty flex min-h-0 flex-1 items-center justify-center"><%= dgettext("ui", "menuEditor.empty") %></div>
|
||||
<% else %>
|
||||
<div id="menu-editor-tree-shell" class="menu-editor-tree-shell" phx-hook="MenuEditorTree">
|
||||
<div id="menu-editor-tree-shell" class="menu-editor-tree-shell min-h-0 flex-1 overflow-auto" phx-hook="MenuEditorTree">
|
||||
<ul class="menu-editor-tree-level">
|
||||
<.menu_tree_level items={@menu_editor.items} menu_editor={@menu_editor} depth={0} myself={@myself} />
|
||||
</ul>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class={["misc-editor-shell", misc_class(@misc_editor.kind)]} data-testid="misc-editor">
|
||||
<div class="misc-editor-header">
|
||||
<div class={["misc-editor-shell flex h-full min-h-0 flex-col overflow-hidden", misc_class(@misc_editor.kind)]} data-testid="misc-editor">
|
||||
<div class="misc-editor-header flex shrink-0 items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2><%= @misc_editor.title %></h2>
|
||||
<p><%= @misc_editor.subtitle %></p>
|
||||
</div>
|
||||
<div class="misc-editor-actions">
|
||||
<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>
|
||||
<% end %>
|
||||
@@ -17,13 +17,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="misc-editor-summary">
|
||||
<div class="misc-editor-summary flex flex-wrap gap-2">
|
||||
<%= for {label, value} <- summary_items(@misc_editor) do %>
|
||||
<div class="misc-summary-pill"><span><%= label %></span><strong><%= value %></strong></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="misc-editor-content">
|
||||
<div class="misc-editor-content min-h-0 flex-1 overflow-auto">
|
||||
<%= case @misc_editor.kind do %>
|
||||
<% :documentation -> %>
|
||||
<div class="documentation-view">
|
||||
|
||||
@@ -30,10 +30,10 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
|
||||
~H"""
|
||||
<%= if Enum.any?(@editor_toolbar_buttons) do %>
|
||||
<div class="editor-toolbar">
|
||||
<div class="editor-toolbar flex items-center gap-2">
|
||||
<%= for button <- @editor_toolbar_buttons do %>
|
||||
<button
|
||||
class={["editor-toolbar-button", if(button.destructive, do: "is-destructive")]}
|
||||
class={["editor-toolbar-button inline-flex items-center justify-center", if(button.destructive, do: "is-destructive")]}
|
||||
data-testid="editor-toolbar-overlay-button"
|
||||
type="button"
|
||||
phx-click="open_overlay"
|
||||
@@ -55,10 +55,10 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
<span><%= dgettext("ui", "No background tasks running") %></span>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="task-list">
|
||||
<div class="task-list flex flex-col gap-2">
|
||||
<%= for task <- Map.get(@task_status, :tasks, []) do %>
|
||||
<div class="panel-entry task-entry">
|
||||
<div class="task-entry-header">
|
||||
<div class="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>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
<span><%= dgettext("ui", "No shell output yet") %></span>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="output-list">
|
||||
<div class="output-list flex flex-col gap-2">
|
||||
<%= for entry <- @output_entries do %>
|
||||
<div class={[
|
||||
"panel-entry",
|
||||
@@ -118,7 +118,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
<span><%= dgettext("ui", "No post links yet") %></span>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="git-log-list">
|
||||
<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>
|
||||
<%= for entry <- @backlinks do %>
|
||||
@@ -165,7 +165,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do
|
||||
|
||||
~H"""
|
||||
<%= if Enum.empty?(@git_entries) do %>
|
||||
<div class="git-log-list">
|
||||
<div class="git-log-list flex flex-col gap-2">
|
||||
<div class="panel-entry panel-empty-state">
|
||||
<strong><%= dgettext("ui", "Git Log") %></strong>
|
||||
<span><%= dgettext("ui", "No git history yet") %></span>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<div class="post-editor editor" data-testid="post-editor">
|
||||
<div class="editor-header">
|
||||
<div class="editor-tabs">
|
||||
<div class={["editor-tab", "active", if(@post_editor.dirty?, do: "dirty")]}>
|
||||
<span class="editor-tab-title" data-testid="editor-title"><%= @post_editor.display_title %></span>
|
||||
<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")]}>
|
||||
<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>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-actions">
|
||||
<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">
|
||||
<%= post_status_label(@post_editor.status) %>
|
||||
</span>
|
||||
@@ -17,9 +17,9 @@
|
||||
<span class="auto-save-indicator"><%= post_editor_save_state_label(@post_editor.save_state) %></span>
|
||||
<% end %>
|
||||
|
||||
<div class="quick-actions-wrapper">
|
||||
<div class="quick-actions-wrapper relative">
|
||||
<button
|
||||
class="secondary quick-actions-btn"
|
||||
class="secondary quick-actions-btn inline-flex items-center gap-2"
|
||||
type="button"
|
||||
phx-click="toggle_post_editor_quick_actions"
|
||||
phx-target={@myself}
|
||||
@@ -29,9 +29,9 @@
|
||||
</button>
|
||||
|
||||
<%= if @post_editor.quick_actions_open? do %>
|
||||
<div class="quick-actions-menu">
|
||||
<div class="quick-actions-menu absolute right-0 top-full z-10 mt-2 flex min-w-72 flex-col">
|
||||
<button
|
||||
class="quick-action-item"
|
||||
class="quick-action-item flex items-start gap-3 text-left"
|
||||
data-testid="editor-toolbar-overlay-button"
|
||||
type="button"
|
||||
phx-click="open_overlay"
|
||||
@@ -39,7 +39,7 @@
|
||||
disabled={not @post_editor.detect_language_enabled?}
|
||||
>
|
||||
<span class="quick-action-icon">🤖</span>
|
||||
<span class="quick-action-text">
|
||||
<span class="quick-action-text flex min-w-0 flex-1 flex-col">
|
||||
<strong><%= dgettext("ui", "AI Suggestions") %></strong>
|
||||
<small><%= dgettext("ui", "Review title, excerpt, and content suggestions") %></small>
|
||||
</span>
|
||||
@@ -48,7 +48,7 @@
|
||||
<div class="quick-actions-divider"></div>
|
||||
|
||||
<button
|
||||
class="quick-action-item"
|
||||
class="quick-action-item flex items-start gap-3 text-left"
|
||||
data-testid="editor-toolbar-overlay-button"
|
||||
type="button"
|
||||
phx-click="open_overlay"
|
||||
@@ -56,7 +56,7 @@
|
||||
disabled={not @post_editor.can_translate?}
|
||||
>
|
||||
<span class="quick-action-icon">🌍</span>
|
||||
<span class="quick-action-text">
|
||||
<span class="quick-action-text flex min-w-0 flex-1 flex-col">
|
||||
<strong><%= dgettext("ui", "Translate") %></strong>
|
||||
<small><%= dgettext("ui", "Select a target language for this post") %></small>
|
||||
</span>
|
||||
@@ -83,14 +83,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="post-editor-form editor-content" data-testid="post-editor-form" phx-change="change_post_editor" phx-target={@myself}>
|
||||
<div class="metadata-toggle-header">
|
||||
<form class="post-editor-form editor-content flex min-h-0 flex-1 flex-col gap-4 overflow-auto" data-testid="post-editor-form" phx-change="change_post_editor" phx-target={@myself}>
|
||||
<div class="metadata-toggle-header flex items-center justify-between gap-3">
|
||||
<button class={["metadata-toggle", if(@post_editor.metadata_expanded, do: "expanded")]} type="button" phx-click="toggle_post_metadata" phx-target={@myself}>
|
||||
<span class="metadata-toggle-chevron"><%= if @post_editor.metadata_expanded, do: "▼", else: "▶" %></span>
|
||||
<span><%= dgettext("ui", "Metadata") %></span>
|
||||
</button>
|
||||
|
||||
<div class="editor-translations-flags" aria-label={dgettext("ui", "Translations")}>
|
||||
<div class="editor-translations-flags flex flex-wrap items-center gap-2" aria-label={dgettext("ui", "Translations")}>
|
||||
<%= for flag <- @post_editor.translation_flags do %>
|
||||
<button
|
||||
class={[
|
||||
@@ -111,18 +111,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class={["editor-header-row", if(not @post_editor.metadata_expanded, do: "is-collapsed")]}>
|
||||
<div class="editor-meta">
|
||||
<div class="editor-field">
|
||||
<div class={["editor-header-row grid gap-4 xl:grid-cols-[minmax(0,2fr)_minmax(280px,1fr)]", if(not @post_editor.metadata_expanded, do: "is-collapsed")]}>
|
||||
<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"]} />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<div class="editor-field flex flex-col gap-1.5">
|
||||
<label><%= dgettext("ui", "Tags") %></label>
|
||||
<div class="tag-input-container">
|
||||
<div class="tag-input-container relative">
|
||||
<input type="hidden" name="post_editor[tags]" value={@post_editor.form["tags"]} />
|
||||
<div class="tag-input-wrapper">
|
||||
<div class="tag-input-wrapper flex flex-wrap items-center gap-2">
|
||||
<%= for tag <- @post_editor.tag_chips do %>
|
||||
<span class={["tag-chip", if(tag.color, do: "has-color")]} style={tag_chip_style(tag.color)}>
|
||||
<span><%= tag.name %></span>
|
||||
@@ -141,7 +141,7 @@
|
||||
</div>
|
||||
|
||||
<%= if String.trim(@post_editor.tag_query || "") != "" and (Enum.any?(@post_editor.tag_suggestions) or @post_editor.tag_query_addable?) do %>
|
||||
<div class="tag-suggestions">
|
||||
<div class="tag-suggestions mt-2 flex flex-col">
|
||||
<%= for tag <- @post_editor.tag_suggestions do %>
|
||||
<button class="tag-suggestion" type="button" phx-click="add_post_editor_tag" phx-value-tag={tag.name} phx-target={@myself}>
|
||||
<%= if tag.color do %>
|
||||
@@ -162,14 +162,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<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"]} />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<div class="editor-field flex flex-col gap-1.5">
|
||||
<label><%= dgettext("ui", "Language") %></label>
|
||||
<div class="editor-language-row">
|
||||
<div class="editor-language-row flex items-center gap-2">
|
||||
<select class="post-editor-input" name="post_editor[language]">
|
||||
<%= for language <- @post_editor.languages do %>
|
||||
<option value={language} selected={language == @post_editor.form["language"]}><%= String.upcase(language) %></option>
|
||||
@@ -189,7 +189,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<div class="editor-field flex flex-col gap-1.5">
|
||||
<label class="editor-checkbox-label">
|
||||
<input type="hidden" name="post_editor[do_not_translate]" value="false" />
|
||||
<input type="checkbox" name="post_editor[do_not_translate]" value="true" checked={@post_editor.form["do_not_translate"]} />
|
||||
@@ -197,17 +197,17 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="editor-field-row">
|
||||
<div class="editor-field">
|
||||
<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} />
|
||||
</div>
|
||||
|
||||
<div class="editor-field">
|
||||
<div class="editor-field flex flex-col gap-1.5">
|
||||
<label><%= dgettext("ui", "Categories") %></label>
|
||||
<div class="tag-input-container">
|
||||
<div class="tag-input-container relative">
|
||||
<input type="hidden" name="post_editor[categories]" value={@post_editor.form["categories"]} />
|
||||
<div class="tag-input-wrapper">
|
||||
<div class="tag-input-wrapper flex flex-wrap items-center gap-2">
|
||||
<%= for category <- @post_editor.category_values do %>
|
||||
<span class="tag-chip">
|
||||
<span><%= category %></span>
|
||||
@@ -226,7 +226,7 @@
|
||||
</div>
|
||||
|
||||
<%= if String.trim(@post_editor.category_query || "") != "" and (Enum.any?(@post_editor.category_suggestions) or @post_editor.category_query_addable?) do %>
|
||||
<div class="tag-suggestions">
|
||||
<div class="tag-suggestions mt-2 flex flex-col">
|
||||
<%= for category <- @post_editor.category_suggestions do %>
|
||||
<button class="tag-suggestion" type="button" phx-click="add_post_editor_category" phx-value-category={category} phx-target={@myself}>
|
||||
<span class="tag-suggestion-name"><%= category %></span>
|
||||
@@ -246,7 +246,7 @@
|
||||
</div>
|
||||
|
||||
<%= if @post_editor.show_template_selector? do %>
|
||||
<div class="editor-field">
|
||||
<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]">
|
||||
<option value=""><%= dgettext("ui", "Default") %></option>
|
||||
@@ -257,9 +257,9 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="post-editor-links-panel">
|
||||
<div class="post-editor-links-panel flex flex-col gap-3">
|
||||
<strong><%= dgettext("ui", "Post Links") %></strong>
|
||||
<div class="post-editor-links-columns">
|
||||
<div class="post-editor-links-columns grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<span class="post-editor-links-label"><%= dgettext("ui", "Backlinks") %></span>
|
||||
<%= if Enum.any?(@post_editor.post_links.backlinks) do %>
|
||||
@@ -288,15 +288,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="editor-media-panel post-editor-side-panel">
|
||||
<aside class="editor-media-panel post-editor-side-panel flex flex-col gap-3">
|
||||
<div class="post-editor-side-panel-header">
|
||||
<strong><%= dgettext("ui", "Linked Media") %></strong>
|
||||
</div>
|
||||
|
||||
<%= if Enum.any?(@post_editor.linked_media) do %>
|
||||
<ul class="post-editor-media-list">
|
||||
<ul class="post-editor-media-list flex flex-col gap-2">
|
||||
<%= for item <- @post_editor.linked_media do %>
|
||||
<li class="post-editor-media-item">
|
||||
<li class="post-editor-media-item flex flex-col gap-1">
|
||||
<span class="post-editor-media-title"><%= item.name %></span>
|
||||
<span class="post-editor-media-meta"><%= dgettext("ui", "Order") %>: <%= item.sort_order %></span>
|
||||
</li>
|
||||
@@ -314,19 +314,19 @@
|
||||
</button>
|
||||
|
||||
<div class={["editor-excerpt-panel", if(not @post_editor.excerpt_expanded, do: "is-collapsed")]}>
|
||||
<div class="editor-field">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-body">
|
||||
<div class="editor-toolbar">
|
||||
<div class="editor-toolbar-left">
|
||||
<div class="editor-body flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="editor-toolbar flex items-center gap-3">
|
||||
<div class="editor-toolbar-left flex items-center gap-2">
|
||||
<label><%= dgettext("ui", "Content") %></label>
|
||||
</div>
|
||||
|
||||
<div class="editor-toolbar-center">
|
||||
<div class="editor-toolbar-center flex flex-1 justify-center">
|
||||
<div class="editor-mode-toggle">
|
||||
<%= for mode <- [:markdown, :preview] do %>
|
||||
<button
|
||||
@@ -342,7 +342,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-toolbar-right">
|
||||
<div class="editor-toolbar-right flex items-center gap-2">
|
||||
<%= if @post_editor.mode == :markdown do %>
|
||||
<button
|
||||
class="insert-post-link-button"
|
||||
@@ -379,7 +379,7 @@
|
||||
</div>
|
||||
|
||||
<%= if @post_editor.mode == :preview do %>
|
||||
<div class="editor-preview post-editor-preview" data-testid="post-editor-preview">
|
||||
<div class="editor-preview post-editor-preview flex min-h-0 flex-1" data-testid="post-editor-preview">
|
||||
<%= if @post_editor.preview_url do %>
|
||||
<iframe class="editor-preview-frame" src={@post_editor.preview_url}></iframe>
|
||||
<% else %>
|
||||
@@ -398,14 +398,14 @@
|
||||
data-monaco-word-wrap="on"
|
||||
data-monaco-insert-event="post-editor-insert-content"
|
||||
>
|
||||
<div id={"post-editor-monaco-#{@post_editor.id}"} class="monaco-editor-instance" phx-update="ignore"></div>
|
||||
<div id={"post-editor-monaco-#{@post_editor.id}"} class="monaco-editor-instance min-h-0 flex-1" phx-update="ignore"></div>
|
||||
<textarea id={"post-editor-content-#{@post_editor.id}"} class="monaco-editor-input post-editor-content" data-testid="post-editor-content" data-post-editor-id={@post_editor.id} name="post_editor[content]" rows="18" spellcheck="false"><%= @post_editor.form["content"] %></textarea>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="editor-footer">
|
||||
<div class="editor-footer flex shrink-0 flex-wrap gap-4">
|
||||
<span><strong><%= dgettext("ui", "Created") %>:</strong> <%= @post_editor.footer.created_at %></span>
|
||||
<span><strong><%= dgettext("ui", "Updated") %>:</strong> <%= @post_editor.footer.updated_at %></span>
|
||||
<%= if @post_editor.footer.published_at do %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="scripts-view-shell editor" data-testid="script-editor">
|
||||
<div class="editor-header scripts-header">
|
||||
<div class="editor-tabs"><div class="editor-tab active"><span class="editor-tab-title"><%= @script_editor.title %></span></div></div>
|
||||
<div class="editor-actions">
|
||||
<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-actions flex flex-wrap items-center justify-end gap-2">
|
||||
<span class={[
|
||||
"status-badge",
|
||||
"status-#{@script_editor.status}"
|
||||
@@ -15,35 +15,35 @@
|
||||
<button class="secondary danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
|
||||
</div>
|
||||
</div>
|
||||
<form class="editor-content scripts-view" phx-change="change_script_editor" phx-target={@myself}>
|
||||
<div class="editor-header-row scripts-meta-row">
|
||||
<div class="editor-meta">
|
||||
<div class="editor-field-row">
|
||||
<div class="editor-field"><label><%= dgettext("ui", "Title") %></label><input type="text" name="script_editor[title]" value={@script_editor.title} /></div>
|
||||
<div class="editor-field"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="script_editor[slug]" value={@script_editor.slug} /></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>
|
||||
<div class="editor-field-row">
|
||||
<div class="editor-field"><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"><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 scripts-enabled-field"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></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 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 class="editor-body scripts-editor">
|
||||
<div class="editor-toolbar scripts-toolbar"><div class="editor-toolbar-left"><label><%= dgettext("ui", "Content") %></label></div></div>
|
||||
<div class="editor-body scripts-editor flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="editor-toolbar scripts-toolbar flex items-center gap-3"><div class="editor-toolbar-left flex items-center gap-2"><label><%= dgettext("ui", "Content") %></label></div></div>
|
||||
<div
|
||||
id={"script-editor-monaco-shell-#{@script_editor.id}"}
|
||||
class="scripts-monaco monaco-editor-shell"
|
||||
class="scripts-monaco monaco-editor-shell min-h-0 flex-1 overflow-hidden"
|
||||
phx-hook="MonacoEditor"
|
||||
data-monaco-editor-id={@script_editor.id}
|
||||
data-monaco-input-id={"script-editor-content-#{@script_editor.id}"}
|
||||
data-monaco-language="lua"
|
||||
data-monaco-word-wrap="on"
|
||||
>
|
||||
<div id={"script-editor-monaco-#{@script_editor.id}"} class="monaco-editor-instance" phx-update="ignore"></div>
|
||||
<div id={"script-editor-monaco-#{@script_editor.id}"} class="monaco-editor-instance min-h-0 flex-1" phx-update="ignore"></div>
|
||||
<textarea id={"script-editor-content-#{@script_editor.id}"} class="monaco-editor-input code-editor-textarea" name="script_editor[content]" spellcheck="false"><%= @script_editor.content %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-footer"><span class="text-muted text-small"><%= dgettext("ui", "Created") %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.created_at) %></span><span class="text-muted text-small"><%= dgettext("ui", "Updated") %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.updated_at) %></span></div>
|
||||
<div class="editor-footer flex shrink-0 flex-wrap gap-4"><span class="text-muted text-small"><%= dgettext("ui", "Created") %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.created_at) %></span><span class="text-muted text-small"><%= dgettext("ui", "Updated") %>: <%= BDS.Persistence.timestamp_to_iso8601(@script_editor.updated_at) %></span></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<div
|
||||
id="settings-editor-shell"
|
||||
class="settings-view-shell"
|
||||
class="settings-view-shell flex h-full min-h-0 flex-col overflow-hidden"
|
||||
data-testid="settings-editor"
|
||||
phx-hook="SettingsSectionScroll"
|
||||
data-selected-settings-section={@settings_editor.selected_section}
|
||||
data-settings-scroll-target={"settings-section-#{@settings_editor.selected_section}"}
|
||||
>
|
||||
<div class="settings-view">
|
||||
<div class="settings-header">
|
||||
<div class="settings-view flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<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" 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")} />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<div class="settings-content min-h-0 flex-1 overflow-auto">
|
||||
<%= if Enum.empty?(@settings_editor.active_sections) do %>
|
||||
<div class="settings-no-results">
|
||||
<div class="settings-no-results flex items-center justify-center py-6">
|
||||
<p><%= dgettext("ui", "No settings match the current search") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -56,25 +56,25 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
|
||||
if is_map(filters) and Map.get(filters, :enabled) do
|
||||
~H"""
|
||||
<form class="search-box" data-testid="sidebar-search-form" phx-change="update_sidebar_search" phx-submit="update_sidebar_search">
|
||||
<form class="search-box flex items-center gap-2" data-testid="sidebar-search-form" phx-change="update_sidebar_search" phx-submit="update_sidebar_search">
|
||||
<input
|
||||
type="text"
|
||||
name="sidebar_filters[search]"
|
||||
value={Map.get(@selected_filters, :search) || ""}
|
||||
placeholder={@sidebar_filters_config.search_placeholder}
|
||||
/>
|
||||
<button type="submit" title={dgettext("ui", "Search")}>
|
||||
<button class="inline-flex h-8 w-8 items-center justify-center" type="submit" title={dgettext("ui", "Search")}>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M15.7 14.3l-4.2-4.2c-.2-.2-.5-.3-.8-.3.9-1.1 1.5-2.5 1.5-4C12.2 2.6 9.6 0 6.4 0S.6 2.6.6 5.8s2.6 5.8 5.8 5.8c1.5 0 2.9-.5 4-1.4 0 .3.1.6.3.8l4.2 4.2c.2.2.5.3.7.3s.5-.1.7-.3c.4-.4.4-1 0-1.4zm-9.3-4c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<%= if Map.get(@selected_filters, :search) do %>
|
||||
<button class="clear-search" data-testid="sidebar-clear-search" type="button" phx-click="clear_sidebar_search">×</button>
|
||||
<button class="clear-search inline-flex h-8 w-8 items-center justify-center" data-testid="sidebar-clear-search" type="button" phx-click="clear_sidebar_search">×</button>
|
||||
<% end %>
|
||||
</form>
|
||||
|
||||
<%= if Map.get(@sidebar_filters_config, :has_active_filters) do %>
|
||||
<div class="filter-status">
|
||||
<div class="filter-status flex items-center justify-between gap-2">
|
||||
<span>
|
||||
<%= @sidebar_filters_config.results_label %>: <%= @sidebar_filters_config.loaded_count %>/<%= @sidebar_filters_config.total_count %>
|
||||
</span>
|
||||
@@ -239,7 +239,7 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
|
||||
if Map.get(filters, :has_more) do
|
||||
~H"""
|
||||
<div class="sidebar-load-more">
|
||||
<div class="sidebar-load-more flex justify-center pt-2">
|
||||
<button class="load-more-button" data-testid="sidebar-load-more" type="button" phx-click="load_more_sidebar">
|
||||
<%= dgettext("ui", "Load more") %>
|
||||
</button>
|
||||
@@ -270,9 +270,9 @@ defmodule BDS.Desktop.ShellLive.SidebarComponents do
|
||||
<span data-testid="sidebar-section-title"><%= section.title %></span>
|
||||
<span class="sidebar-section-count"><%= Map.get(section, :count, length(Map.get(section, :items, []))) %></span>
|
||||
</div>
|
||||
<div class="sidebar-list">
|
||||
<div class="sidebar-list flex flex-col">
|
||||
<%= for item <- Map.get(section, :items, []) do %>
|
||||
<div class="sidebar-item-row" data-item-id={item.id}>
|
||||
<div class="sidebar-item-row flex items-center gap-2" data-item-id={item.id}>
|
||||
<button
|
||||
class={["sidebar-item", "sidebar-post-item", "post-type-post", if(sidebar_item_selected?(@workbench, item.route, item.id), do: "selected")]}
|
||||
data-testid="sidebar-open-item"
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<div
|
||||
id="tags-editor-shell"
|
||||
class="tags-view-shell"
|
||||
class="tags-view-shell flex h-full min-h-0 flex-col overflow-hidden"
|
||||
data-testid="tags-editor"
|
||||
phx-hook="TagsSectionScroll"
|
||||
data-selected-tags-section={@tags_editor.selected_section}
|
||||
data-tags-scroll-target={"tags-section-#{@tags_editor.selected_section}"}
|
||||
>
|
||||
<div class="tags-view">
|
||||
<div class="tags-view-header">
|
||||
<div class="tags-view flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="tags-view-header flex shrink-0 items-center justify-between gap-3">
|
||||
<h2><%= dgettext("ui", "Tags") %></h2>
|
||||
</div>
|
||||
|
||||
<div class="tags-view-content">
|
||||
<div class="tags-view-content flex min-h-0 flex-1 flex-col gap-4 overflow-auto">
|
||||
<div class="tags-section" id="tags-section-cloud">
|
||||
<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">
|
||||
<div class="tags-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>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="tag-cloud">
|
||||
<div class="tag-cloud flex flex-wrap gap-2">
|
||||
<%= for tag <- @tags_editor.tags do %>
|
||||
<button class={["tag-cloud-item", if(tag.name in @tags_editor.selected, do: "selected"), if(tag.color, do: "has-color")]} style={tag_style(tag, @tags_editor.tags)} type="button" phx-click="toggle_tag_selection" phx-value-name={tag.name} phx-target={@myself}>
|
||||
<%= tag.name %><span class="tag-count"><%= tag.count %></span>
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="tags-section-header"><h3><%= dgettext("ui", "Create / Edit") %></h3></div>
|
||||
<div class="tags-section-content">
|
||||
<form class="tag-create-form" phx-change="change_new_tag_editor" phx-target={@myself}>
|
||||
<div class="tag-form-row">
|
||||
<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 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>
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<%= 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">
|
||||
<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 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]">
|
||||
@@ -65,8 +65,8 @@
|
||||
<div class="tags-section" id="tags-section-merge">
|
||||
<div class="tags-section-header"><h3><%= dgettext("ui", "Merge Tags") %></h3></div>
|
||||
<div class="tags-section-content">
|
||||
<div class="merge-form">
|
||||
<div class="tag-form-row">
|
||||
<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}>
|
||||
<%= for tag_name <- @tags_editor.selected do %>
|
||||
<option value={tag_name} selected={tag_name == @tags_editor.merge_target}><%= tag_name %></option>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="templates-view-shell editor" data-testid="template-editor">
|
||||
<div class="editor-header templates-header">
|
||||
<div class="editor-tabs"><div class="editor-tab active"><span class="editor-tab-title"><%= @template_editor.title %></span></div></div>
|
||||
<div class="editor-actions">
|
||||
<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-actions flex flex-wrap items-center justify-end gap-2">
|
||||
<span class={[
|
||||
"status-badge",
|
||||
"status-#{@template_editor.status}"
|
||||
@@ -14,34 +14,34 @@
|
||||
<button class="secondary danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
|
||||
</div>
|
||||
</div>
|
||||
<form class="editor-content templates-view" phx-change="change_template_editor" phx-target={@myself}>
|
||||
<div class="editor-header-row templates-meta-row">
|
||||
<div class="editor-meta">
|
||||
<div class="editor-field-row">
|
||||
<div class="editor-field"><label><%= dgettext("ui", "Title") %></label><input type="text" name="template_editor[title]" value={@template_editor.title} /></div>
|
||||
<div class="editor-field"><label><%= dgettext("ui", "Slug") %></label><input type="text" name="template_editor[slug]" value={@template_editor.slug} /></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>
|
||||
<div class="editor-field-row">
|
||||
<div class="editor-field"><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 templates-enabled-field"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></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 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 class="editor-body templates-editor">
|
||||
<div class="editor-toolbar templates-toolbar"><div class="editor-toolbar-left"><label><%= dgettext("ui", "Content") %></label></div></div>
|
||||
<div class="editor-body templates-editor flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="editor-toolbar templates-toolbar flex items-center gap-3"><div class="editor-toolbar-left flex items-center gap-2"><label><%= dgettext("ui", "Content") %></label></div></div>
|
||||
<div
|
||||
id={"template-editor-monaco-shell-#{@template_editor.id}"}
|
||||
class="templates-monaco monaco-editor-shell"
|
||||
class="templates-monaco monaco-editor-shell min-h-0 flex-1 overflow-hidden"
|
||||
phx-hook="MonacoEditor"
|
||||
data-monaco-editor-id={@template_editor.id}
|
||||
data-monaco-input-id={"template-editor-content-#{@template_editor.id}"}
|
||||
data-monaco-language="liquid"
|
||||
data-monaco-word-wrap="on"
|
||||
>
|
||||
<div id={"template-editor-monaco-#{@template_editor.id}"} class="monaco-editor-instance" phx-update="ignore"></div>
|
||||
<div id={"template-editor-monaco-#{@template_editor.id}"} class="monaco-editor-instance min-h-0 flex-1" phx-update="ignore"></div>
|
||||
<textarea id={"template-editor-content-#{@template_editor.id}"} class="monaco-editor-input code-editor-textarea" name="template_editor[content]" spellcheck="false"><%= @template_editor.content %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-footer"><span class="text-muted text-small"><%= dgettext("ui", "Created") %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.created_at) %></span><span class="text-muted text-small"><%= dgettext("ui", "Updated") %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.updated_at) %></span></div>
|
||||
<div class="editor-footer flex shrink-0 flex-wrap gap-4"><span class="text-muted text-small"><%= dgettext("ui", "Created") %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.created_at) %></span><span class="text-muted text-small"><%= dgettext("ui", "Updated") %>: <%= BDS.Persistence.timestamp_to_iso8601(@template_editor.updated_at) %></span></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user