feat: phase 5 of tailwind migration

This commit is contained in:
2026-05-04 12:02:13 +02:00
parent 8e715eec8b
commit eca89e51d2
14 changed files with 439 additions and 493 deletions

View File

@@ -9,18 +9,10 @@
} }
.chat-panel { .chat-panel {
display: flex;
min-height: 0;
flex-direction: column;
color: var(--vscode-editor-foreground); color: var(--vscode-editor-foreground);
} }
.chat-panel-header { .chat-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 16px;
border-bottom: 1px solid var(--vscode-panel-border); border-bottom: 1px solid var(--vscode-panel-border);
background: var(--vscode-sideBar-background); background: var(--vscode-sideBar-background);
} }
@@ -28,8 +20,6 @@
.chat-panel-title { .chat-panel-title {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
display: flex;
align-items: center;
gap: 10px; gap: 10px;
overflow: visible; overflow: visible;
font-size: 14px; font-size: 14px;
@@ -91,10 +81,6 @@
} }
.chat-messages { .chat-messages {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
} }
.chat-message { .chat-message {
@@ -152,9 +138,6 @@
} }
.chat-panel .chat-input-wrapper { .chat-panel .chat-input-wrapper {
display: flex;
align-items: flex-end;
gap: 8px;
min-height: 30px; min-height: 30px;
border: 1px solid var(--vscode-input-border); border: 1px solid var(--vscode-input-border);
border-radius: 6px; border-radius: 6px;
@@ -204,3 +187,37 @@
.chat-panel .chat-send-button:disabled { .chat-panel .chat-send-button:disabled {
opacity: 0.5; opacity: 0.5;
} }
@media (max-width: 720px) {
.chat-panel-header {
align-items: stretch;
flex-direction: column;
padding: 10px 12px;
}
.chat-panel-title {
width: 100%;
flex-wrap: wrap;
}
.chat-model-selector-wrap {
width: 100%;
}
.chat-panel .chat-model-selector-button.chat-model-selector-inline {
justify-content: space-between;
width: 100%;
}
.chat-messages {
padding: 12px;
}
.chat-message-content {
max-width: 100%;
}
.chat-panel .chat-input-container {
padding: 8px 12px;
}
}

View File

@@ -321,58 +321,6 @@
overflow: hidden; overflow: hidden;
} }
.post-editor .editor-header,
.scripts-view-shell.editor .editor-header,
.templates-view-shell.editor .editor-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 12px;
min-height: 35px;
background-color: var(--vscode-tab-activeBackground);
border-bottom: 1px solid var(--vscode-panel-border);
}
.post-editor .editor-tabs,
.scripts-view-shell.editor .editor-tabs,
.templates-view-shell.editor .editor-tabs {
display: flex;
align-items: center;
gap: 2px;
min-width: 0;
}
.post-editor .editor-tab,
.scripts-view-shell.editor .editor-tab,
.templates-view-shell.editor .editor-tab {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
padding: 6px 12px;
background-color: var(--vscode-tab-inactiveBackground);
color: var(--vscode-tab-inactiveForeground);
font-size: 13px;
border-radius: 4px 4px 0 0;
}
.post-editor .editor-tab.active,
.scripts-view-shell.editor .editor-tab.active,
.templates-view-shell.editor .editor-tab.active {
background-color: var(--vscode-tab-activeBackground);
color: var(--vscode-tab-activeForeground);
}
.post-editor .editor-tab-title,
.scripts-view-shell.editor .editor-tab-title,
.templates-view-shell.editor .editor-tab-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.post-editor .editor-tab-dirty { .post-editor .editor-tab-dirty {
color: var(--vscode-notificationsWarningIcon-foreground, var(--vscode-editorWarning-foreground)); color: var(--vscode-notificationsWarningIcon-foreground, var(--vscode-editorWarning-foreground));
font-size: 10px; font-size: 10px;
@@ -384,14 +332,6 @@
white-space: nowrap; white-space: nowrap;
} }
.post-editor .editor-actions,
.scripts-view-shell.editor .editor-actions,
.templates-view-shell.editor .editor-actions {
display: flex;
align-items: center;
gap: 8px;
}
.post-editor .quick-actions-wrapper { .post-editor .quick-actions-wrapper {
position: relative; position: relative;
display: inline-block; display: inline-block;
@@ -409,60 +349,17 @@
line-height: 1; line-height: 1;
} }
.post-editor .quick-actions-menu {
position: absolute;
top: 100%;
right: 0;
margin-top: 4px;
min-width: 280px;
background: var(--vscode-dropdown-background, #3c3c3c);
border: 1px solid var(--vscode-dropdown-border, #454545);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1000;
overflow: hidden;
}
.post-editor .quick-actions-divider { .post-editor .quick-actions-divider {
height: 1px; height: 1px;
background: var(--vscode-dropdown-border, #454545); background: var(--vscode-dropdown-border, #454545);
} }
.post-editor .quick-action-item {
display: flex;
align-items: flex-start;
gap: 10px;
width: 100%;
padding: 10px 12px;
background: none;
border: none;
color: var(--vscode-dropdown-foreground, #ccc);
cursor: pointer;
text-align: left;
transition: background 0.1s;
}
.post-editor .quick-action-item:hover:not(:disabled) {
background: var(--vscode-list-hoverBackground, #2a2d2e);
}
.post-editor .quick-action-item:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.post-editor .quick-action-icon { .post-editor .quick-action-icon {
font-size: 16px; font-size: 16px;
flex-shrink: 0; flex-shrink: 0;
margin-top: 2px; margin-top: 2px;
} }
.post-editor .quick-action-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.post-editor .quick-action-text strong { .post-editor .quick-action-text strong {
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
@@ -510,19 +407,7 @@
font-style: italic; font-style: italic;
} }
.post-editor .editor-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
overflow-y: auto;
}
.post-editor .metadata-toggle-header { .post-editor .metadata-toggle-header {
display: flex;
align-items: center;
gap: 8px;
} }
.post-editor .metadata-toggle { .post-editor .metadata-toggle {
@@ -550,37 +435,15 @@
font-size: 10px; font-size: 10px;
} }
.post-editor .editor-header-row {
display: flex;
gap: 16px;
align-items: flex-start;
}
.post-editor .editor-header-row.is-collapsed { .post-editor .editor-header-row.is-collapsed {
display: none; display: none;
} }
.post-editor .editor-meta {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
min-width: 0;
}
.post-editor .editor-media-panel { .post-editor .editor-media-panel {
width: 200px; width: 200px;
flex-shrink: 0; flex-shrink: 0;
} }
.post-editor .editor-field {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-width: 200px;
}
.post-editor .editor-field label, .post-editor .editor-field label,
.post-editor .editor-body label, .post-editor .editor-body label,
.post-editor .post-editor-links-label { .post-editor .post-editor-links-label {
@@ -600,27 +463,11 @@
color: var(--vscode-foreground); color: var(--vscode-foreground);
} }
.post-editor .post-editor-input,
.post-editor .post-editor-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;
}
.post-editor .post-editor-input.is-readonly { .post-editor .post-editor-input.is-readonly {
opacity: 0.7; opacity: 0.7;
cursor: not-allowed; cursor: not-allowed;
} }
.post-editor .post-editor-textarea {
line-height: 1.5;
resize: vertical;
}
.post-editor .post-editor-excerpt { .post-editor .post-editor-excerpt {
min-height: 96px; min-height: 96px;
} }
@@ -796,33 +643,11 @@
font-weight: 600; font-weight: 600;
} }
.post-editor .editor-field-row {
display: flex;
gap: 12px;
width: 100%;
}
.post-editor .editor-language-row {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: nowrap;
}
.post-editor .editor-language-row select { .post-editor .editor-language-row select {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
.post-editor .editor-translations-flags {
display: flex;
gap: 4px;
align-items: center;
flex: 1;
min-width: 0;
overflow-x: auto;
}
.post-editor .editor-translation-flag { .post-editor .editor-translation-flag {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View File

@@ -1,64 +1,8 @@
[data-testid="media-editor"] {
flex: 1;
display: flex;
flex-direction: column;
background-color: var(--vscode-editor-background);
overflow: hidden;
}
[data-testid="media-editor"] .editor-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 12px;
min-height: 35px;
background-color: var(--vscode-tab-activeBackground);
border-bottom: 1px solid var(--vscode-panel-border);
}
[data-testid="media-editor"] .editor-tabs {
display: flex;
align-items: center;
gap: 2px;
min-width: 0;
}
[data-testid="media-editor"] .editor-tab {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
padding: 6px 12px;
background-color: var(--vscode-tab-inactiveBackground);
color: var(--vscode-tab-inactiveForeground);
font-size: 13px;
border-radius: 4px 4px 0 0;
}
[data-testid="media-editor"] .editor-tab.active {
background-color: var(--vscode-tab-activeBackground);
color: var(--vscode-tab-activeForeground);
}
[data-testid="media-editor"] .editor-tab-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-testid="media-editor"] .editor-tab-dirty { [data-testid="media-editor"] .editor-tab-dirty {
color: var(--vscode-notificationsWarningIcon-foreground, var(--vscode-editorWarning-foreground)); color: var(--vscode-notificationsWarningIcon-foreground, var(--vscode-editorWarning-foreground));
font-size: 10px; font-size: 10px;
} }
[data-testid="media-editor"] .editor-actions {
display: flex;
align-items: center;
gap: 8px;
}
[data-testid="media-editor"] .editor-actions button { [data-testid="media-editor"] .editor-actions button {
padding: 4px 10px; padding: 4px 10px;
font-size: 12px; font-size: 12px;
@@ -89,62 +33,17 @@
line-height: 1; line-height: 1;
} }
[data-testid="media-editor"] .quick-actions-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
width: 280px;
background: var(--vscode-dropdown-background, #252526);
border: 1px solid var(--vscode-dropdown-border, #454545);
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
overflow: hidden;
z-index: 30;
}
[data-testid="media-editor"] .quick-actions-divider { [data-testid="media-editor"] .quick-actions-divider {
height: 1px; height: 1px;
background: var(--vscode-dropdown-border, #454545); background: var(--vscode-dropdown-border, #454545);
} }
[data-testid="media-editor"] .quick-action-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
width: 100%;
padding: 10px 12px;
background: none;
border: none;
color: var(--vscode-dropdown-foreground, #ccc);
cursor: pointer;
text-align: left;
transition: background 0.1s;
}
[data-testid="media-editor"] .quick-action-item:hover:not(:disabled) {
background: var(--vscode-list-hoverBackground, #2a2d2e);
}
[data-testid="media-editor"] .quick-action-item:disabled {
opacity: 0.5;
cursor: not-allowed;
}
[data-testid="media-editor"] .quick-action-icon { [data-testid="media-editor"] .quick-action-icon {
font-size: 16px; font-size: 16px;
flex-shrink: 0; flex-shrink: 0;
margin-top: 2px; margin-top: 2px;
} }
[data-testid="media-editor"] .quick-action-text {
display: flex;
flex: 1;
flex-direction: column;
gap: 2px;
min-width: 0;
}
[data-testid="media-editor"] .quick-action-text strong { [data-testid="media-editor"] .quick-action-text strong {
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
@@ -155,29 +54,12 @@
opacity: 0.7; opacity: 0.7;
} }
[data-testid="media-editor"] .editor-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 16px;
overflow-y: auto;
gap: 16px;
}
[data-testid="media-editor"] > .editor-content.media-editor { [data-testid="media-editor"] > .editor-content.media-editor {
flex-direction: row; flex-direction: row;
align-items: stretch; align-items: stretch;
gap: 24px; gap: 24px;
} }
[data-testid="media-editor"] .editor-field {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-width: 0;
}
[data-testid="media-editor"] .editor-field label { [data-testid="media-editor"] .editor-field label {
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
@@ -186,35 +68,12 @@
letter-spacing: 0.5px; letter-spacing: 0.5px;
} }
[data-testid="media-editor"] .editor-field-row {
display: flex;
gap: 12px;
width: 100%;
margin-bottom: 0;
}
[data-testid="media-editor"] .post-editor-input,
[data-testid="media-editor"] .post-editor-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;
}
[data-testid="media-editor"] .post-editor-input.disabled, [data-testid="media-editor"] .post-editor-input.disabled,
[data-testid="media-editor"] .post-editor-input:disabled { [data-testid="media-editor"] .post-editor-input:disabled {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
[data-testid="media-editor"] .post-editor-textarea {
line-height: 1.5;
resize: vertical;
}
[data-testid="media-editor"] .media-preview { [data-testid="media-editor"] .media-preview {
flex: 1; flex: 1;
display: flex; display: flex;
@@ -255,18 +114,10 @@
[data-testid="media-editor"] .media-details { [data-testid="media-editor"] .media-details {
width: 320px; width: 320px;
display: flex;
flex-direction: column;
gap: 12px; gap: 12px;
flex-shrink: 0; flex-shrink: 0;
} }
[data-testid="media-editor"] .media-editor-details-form {
display: flex;
flex-direction: column;
gap: 12px;
}
[data-testid="media-editor"] .media-details textarea { [data-testid="media-editor"] .media-details textarea {
resize: vertical; resize: vertical;
} }

View File

@@ -1,20 +1,4 @@
.menu-editor-view {
padding: 1rem;
height: 100%;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
overflow: hidden;
background: var(--vscode-editor-background);
}
.menu-editor-header { .menu-editor-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
} }
.menu-editor-header h2 { .menu-editor-header h2 {
@@ -26,18 +10,7 @@
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
} }
.menu-editor-main {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
overflow: hidden;
}
.menu-editor-tree-wrap { .menu-editor-tree-wrap {
display: flex;
flex-direction: column;
flex: 1;
border: 1px solid var(--vscode-panel-border); border: 1px solid var(--vscode-panel-border);
border-radius: 6px; border-radius: 6px;
background: var(--vscode-editor-background); background: var(--vscode-editor-background);
@@ -46,9 +19,6 @@
} }
.menu-editor-toolbar { .menu-editor-toolbar {
display: flex;
align-items: center;
gap: 0.2rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
padding-bottom: 0.4rem; padding-bottom: 0.4rem;
border-bottom: 1px solid var(--vscode-panel-border); border-bottom: 1px solid var(--vscode-panel-border);

View File

@@ -130,6 +130,119 @@
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
} }
.ui-editor-shell {
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--vscode-editor-background);
}
.ui-editor-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
min-height: 35px;
padding: 0 12px;
border-bottom: 1px solid var(--vscode-panel-border);
background: var(--vscode-tab-activeBackground);
}
.ui-editor-tab-current {
display: inline-flex;
max-width: 100%;
align-items: center;
gap: 6px;
overflow: hidden;
padding: 6px 12px;
border-radius: 4px 4px 0 0;
background: var(--vscode-tab-activeBackground);
color: var(--vscode-tab-activeForeground);
}
.ui-editor-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
flex-wrap: wrap;
}
.ui-toolbar {
display: flex;
align-items: center;
gap: 12px;
min-height: 32px;
}
.ui-toolbar-group {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.ui-field-stack {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
.ui-field-stack > label,
.ui-field-label {
font-size: 11px;
font-weight: 500;
color: var(--vscode-descriptionForeground);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.ui-field-grid-2,
.ui-field-grid-3 {
display: grid;
gap: 16px;
}
.ui-dropdown-menu {
background: var(--vscode-dropdown-background, var(--vscode-sideBar-background));
border: 1px solid var(--vscode-dropdown-border, var(--vscode-panel-border));
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
overflow: hidden;
}
.ui-dropdown-item {
display: flex;
align-items: flex-start;
gap: 10px;
width: 100%;
padding: 10px 12px;
border: none;
background: transparent;
color: var(--vscode-dropdown-foreground, var(--vscode-foreground));
cursor: pointer;
text-align: left;
transition: background 0.1s;
}
.ui-dropdown-item:hover:not(:disabled) {
background: var(--vscode-list-hoverBackground, #2a2d2e);
}
.ui-dropdown-item:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.ui-section-card {
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
background: color-mix(in srgb, var(--vscode-editor-background) 84%, var(--vscode-input-background));
}
.btn-base { .btn-base {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -176,3 +289,13 @@
overflow: hidden; overflow: hidden;
} }
} }
@media (min-width: 768px) {
.ui-field-grid-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ui-field-grid-3 {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
}
}

View File

@@ -1,5 +1,5 @@
<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 id={"chat-editor-#{@chat_editor.id}"} class="chat-panel ui-editor-shell 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-header flex shrink-0 items-center justify-between gap-3 px-4 py-3">
<div class="chat-panel-title flex min-w-0 flex-1 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"> <span class="chat-panel-title-main">
<%= if @chat_editor.needs_api_key? do %> <%= if @chat_editor.needs_api_key? do %>
@@ -23,7 +23,7 @@
</button> </button>
<%= if @chat_editor.model_selector_open? and @chat_editor.available_models != [] do %> <%= if @chat_editor.model_selector_open? and @chat_editor.available_models != [] do %>
<div class="chat-model-selector-menu absolute right-0 top-full z-10 mt-2 flex min-w-56 flex-col"> <div class="chat-model-selector-menu ui-dropdown-menu absolute right-0 top-full z-10 mt-2 flex min-w-56 flex-col">
<%= for group <- @chat_editor.available_model_groups do %> <%= for group <- @chat_editor.available_model_groups do %>
<section class="chat-model-provider-group" data-testid="chat-model-provider-group" data-provider={group.provider}> <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 %> <%= if length(@chat_editor.available_model_groups) > 1 do %>
@@ -33,7 +33,7 @@
<%= for model <- group.models do %> <%= for model <- group.models do %>
<button <button
class={[ class={[
"chat-model-selector-option flex items-center justify-between gap-2 text-left", "chat-model-selector-option ui-dropdown-item flex items-center justify-between gap-2 text-left",
if(model.id == @chat_editor.effective_model, do: "active") if(model.id == @chat_editor.effective_model, do: "active")
]} ]}
type="button" type="button"
@@ -57,7 +57,7 @@
<div class="chat-messages chat-surface-scroll min-h-0 flex-1 overflow-auto"> <div class="chat-messages chat-surface-scroll min-h-0 flex-1 overflow-auto">
<%= if @chat_editor.needs_api_key? do %> <%= if @chat_editor.needs_api_key? do %>
<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 chat-api-key-state ui-section-card flex flex-col items-start gap-3 p-4" data-testid="chat-api-key-required">
<div class="chat-welcome-icon">🔑</div> <div class="chat-welcome-icon">🔑</div>
<h2><%= dgettext("ui", "API Key Required") %></h2> <h2><%= dgettext("ui", "API Key Required") %></h2>
<p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p> <p><%= dgettext("ui", "Configure an API key in Settings to enable AI chat.") %></p>
@@ -67,7 +67,7 @@
</div> </div>
<% else %> <% else %>
<%= if Enum.empty?(@chat_editor.messages) and not @chat_editor.is_streaming do %> <%= if Enum.empty?(@chat_editor.messages) and not @chat_editor.is_streaming do %>
<div class="chat-welcome flex flex-col items-start gap-3"> <div class="chat-welcome ui-section-card flex flex-col items-start gap-3 p-4">
<div class="chat-welcome-icon">🤖</div> <div class="chat-welcome-icon">🤖</div>
<h2><%= dgettext("ui", "Welcome to the AI Assistant") %></h2> <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> <p><%= dgettext("ui", "I can help you manage your blog with rich visualizations. Try asking me to:") %></p>
@@ -83,7 +83,7 @@
<%= for message <- @chat_editor.messages do %> <%= for message <- @chat_editor.messages do %>
<div class={["chat-message flex items-start gap-3", to_string(message.role || "assistant")]}> <div class={["chat-message flex items-start gap-3", to_string(message.role || "assistant")]}>
<div class="chat-message-avatar"><%= if message.role == :user, do: "👤", else: "🤖" %></div> <div class="chat-message-avatar"><%= if message.role == :user, do: "👤", else: "🤖" %></div>
<div class="chat-message-content"> <div class="chat-message-content ui-section-card">
<div class="chat-message-header"><span class="chat-message-role"><%= message_role_label(message.role) %></span></div> <div class="chat-message-header"><span class="chat-message-role"><%= message_role_label(message.role) %></span></div>
<.chat_tool_markers markers={message.tool_markers} /> <.chat_tool_markers markers={message.tool_markers} />
@@ -103,7 +103,7 @@
<%= if @chat_editor.pending_user_message do %> <%= 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 user pending flex items-start gap-3" data-testid="chat-pending-user-message">
<div class="chat-message-avatar">👤</div> <div class="chat-message-avatar">👤</div>
<div class="chat-message-content"> <div class="chat-message-content ui-section-card">
<div class="chat-message-header"> <div class="chat-message-header">
<span class="chat-message-role"><%= message_role_label(:user) %></span> <span class="chat-message-role"><%= message_role_label(:user) %></span>
</div> </div>
@@ -115,7 +115,7 @@
<%= if @chat_editor.is_streaming and (@chat_editor.streaming_content != "" or @chat_editor.streaming_tool_markers != []) do %> <%= if @chat_editor.is_streaming and (@chat_editor.streaming_content != "" or @chat_editor.streaming_tool_markers != []) do %>
<div class="chat-message assistant streaming flex items-start gap-3" data-testid="chat-streaming-message"> <div class="chat-message assistant streaming flex items-start gap-3" data-testid="chat-streaming-message">
<div class="chat-message-avatar">🤖</div> <div class="chat-message-avatar">🤖</div>
<div class="chat-message-content"> <div class="chat-message-content ui-section-card">
<div class="chat-message-header"> <div class="chat-message-header">
<span class="chat-message-role"><%= message_role_label(:assistant) %></span> <span class="chat-message-role"><%= message_role_label(:assistant) %></span>
<span class="streaming-indicator">●</span> <span class="streaming-indicator">●</span>
@@ -147,7 +147,7 @@
</div> </div>
<%= unless @chat_editor.needs_api_key? do %> <%= unless @chat_editor.needs_api_key? do %>
<div class="chat-input-container flex shrink-0 flex-col gap-3" data-testid="chat-input-container"> <div class="chat-input-container ui-field-stack flex shrink-0 flex-col gap-3" data-testid="chat-input-container">
<%= if @chat_editor.is_streaming do %> <%= if @chat_editor.is_streaming do %>
<button class="chat-abort-button 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> <button class="chat-abort-button ui-button ui-button-secondary" data-testid="chat-abort-button" type="button" phx-click="abort_chat_editor_message" phx-target={@myself}>◼ <%= dgettext("ui", "Stop") %></button>
<% end %> <% end %>

View File

@@ -1,8 +1,8 @@
<div class="media-editor editor flex h-full min-h-0 flex-col" data-testid="media-editor"> <div class="media-editor editor ui-editor-shell 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-header ui-editor-header flex shrink-0 items-start justify-between gap-3">
<div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"> <div class="editor-tabs flex min-w-0 flex-1 overflow-hidden">
<div class={[ <div class={[
"editor-tab ui-tab ui-tab-active active inline-flex max-w-full items-center gap-2 overflow-hidden px-3 py-2", "editor-tab ui-tab ui-tab-active ui-editor-tab-current active inline-flex max-w-full items-center gap-2 overflow-hidden px-3 py-2",
if(@media_editor.dirty?, do: "dirty") if(@media_editor.dirty?, do: "dirty")
]}> ]}>
<span class="editor-tab-title truncate" data-testid="editor-title"><%= @media_editor.display_title %></span> <span class="editor-tab-title truncate" data-testid="editor-title"><%= @media_editor.display_title %></span>
@@ -12,7 +12,7 @@
</div> </div>
</div> </div>
<div class="editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="editor-actions ui-editor-actions flex flex-wrap items-center justify-end gap-2">
<%= if @media_editor.save_state in [:dirty, :saved] do %> <%= if @media_editor.save_state in [:dirty, :saved] do %>
<span class="auto-save-indicator"><%= media_editor_save_state_label(@media_editor.save_state) %></span> <span class="auto-save-indicator"><%= media_editor_save_state_label(@media_editor.save_state) %></span>
<% end %> <% end %>
@@ -29,10 +29,10 @@
</button> </button>
<%= if @media_editor.quick_actions_open? do %> <%= if @media_editor.quick_actions_open? do %>
<div class="quick-actions-menu absolute right-0 top-full z-10 mt-2 flex min-w-72 flex-col"> <div class="quick-actions-menu ui-dropdown-menu absolute right-0 top-full z-10 mt-2 flex min-w-72 flex-col">
<%= if @media_editor.is_image do %> <%= if @media_editor.is_image do %>
<button <button
class="quick-action-item flex items-start gap-3 text-left" class="quick-action-item ui-dropdown-item flex items-start gap-3 text-left"
data-testid="editor-toolbar-overlay-button" data-testid="editor-toolbar-overlay-button"
type="button" type="button"
phx-click="open_overlay" phx-click="open_overlay"
@@ -49,7 +49,7 @@
<% end %> <% end %>
<button <button
class="quick-action-item flex items-start gap-3 text-left" class="quick-action-item ui-dropdown-item flex items-start gap-3 text-left"
type="button" type="button"
phx-click="detect_media_editor_language" phx-click="detect_media_editor_language"
phx-target={@myself} phx-target={@myself}
@@ -65,7 +65,7 @@
<div class="quick-actions-divider"></div> <div class="quick-actions-divider"></div>
<button <button
class="quick-action-item flex items-start gap-3 text-left" class="quick-action-item ui-dropdown-item flex items-start gap-3 text-left"
data-testid="editor-toolbar-overlay-button" data-testid="editor-toolbar-overlay-button"
type="button" type="button"
phx-click="open_overlay" phx-click="open_overlay"
@@ -100,7 +100,7 @@
</div> </div>
</div> </div>
<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="editor-content media-editor grid min-h-0 flex-1 gap-4 overflow-auto p-4 xl:grid-cols-[minmax(320px,1fr)_minmax(0,1.2fr)]">
<div class="media-preview flex min-h-[16rem] items-center justify-center"> <div class="media-preview flex min-h-[16rem] items-center justify-center">
<%= if @media_editor.is_image and @media_editor.preview_url do %> <%= if @media_editor.is_image and @media_editor.preview_url do %>
<div class="media-preview-image"> <div class="media-preview-image">
@@ -118,56 +118,56 @@
<div class="media-details min-w-0"> <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}> <form class="media-editor-details-form flex flex-col gap-4" data-testid="media-editor-form" phx-change="change_media_editor" phx-target={@myself}>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "File Name") %></label> <label><%= dgettext("ui", "File Name") %></label>
<input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.original_name} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.original_name} disabled />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "MIME Type") %></label> <label><%= dgettext("ui", "MIME Type") %></label>
<input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.mime_type} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.mime_type} disabled />
</div> </div>
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row ui-field-grid-2 grid gap-4 md:grid-cols-2">
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Size") %></label> <label><%= dgettext("ui", "Size") %></label>
<input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.file_size} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.file_size} disabled />
</div> </div>
<%= if @media_editor.dimensions do %> <%= if @media_editor.dimensions do %>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Dimensions") %></label> <label><%= dgettext("ui", "Dimensions") %></label>
<input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.dimensions} disabled /> <input class="post-editor-input ui-input disabled ui-input-disabled" type="text" value={@media_editor.dimensions} disabled />
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input ui-input" type="text" name="media_editor[title]" value={@media_editor.form["title"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[title]" value={@media_editor.form["title"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Alt Text") %></label> <label><%= dgettext("ui", "Alt Text") %></label>
<input class="post-editor-input ui-input" type="text" name="media_editor[alt]" value={@media_editor.form["alt"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[alt]" value={@media_editor.form["alt"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Caption") %></label> <label><%= dgettext("ui", "Caption") %></label>
<textarea class="post-editor-textarea ui-textarea" name="media_editor[caption]" rows="3"><%= @media_editor.form["caption"] %></textarea> <textarea class="post-editor-textarea ui-textarea" name="media_editor[caption]" rows="3"><%= @media_editor.form["caption"] %></textarea>
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Tags") %></label> <label><%= dgettext("ui", "Tags") %></label>
<input class="post-editor-input ui-input" type="text" name="media_editor[tags]" value={@media_editor.form["tags"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[tags]" value={@media_editor.form["tags"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Author") %></label> <label><%= dgettext("ui", "Author") %></label>
<input class="post-editor-input ui-input" type="text" name="media_editor[author]" value={@media_editor.form["author"]} /> <input class="post-editor-input ui-input" type="text" name="media_editor[author]" value={@media_editor.form["author"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Language") %></label> <label><%= dgettext("ui", "Language") %></label>
<select class="post-editor-input ui-input" name="media_editor[language]"> <select class="post-editor-input ui-input" name="media_editor[language]">
<option value=""><%= dgettext("ui", "None") %></option> <option value=""><%= dgettext("ui", "None") %></option>
@@ -281,15 +281,15 @@
</div> </div>
<form class="translation-modal-body flex flex-col gap-4 overflow-auto" 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"]} /> <input type="hidden" name="media_translation[language]" value={@media_editor.editing_translation["language"]} />
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input ui-input" type="text" name="media_translation[title]" value={@media_editor.editing_translation["title"]} /> <input class="post-editor-input ui-input" type="text" name="media_translation[title]" value={@media_editor.editing_translation["title"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Alt Text") %></label> <label><%= dgettext("ui", "Alt Text") %></label>
<input class="post-editor-input ui-input" type="text" name="media_translation[alt]" value={@media_editor.editing_translation["alt"]} /> <input class="post-editor-input ui-input" type="text" name="media_translation[alt]" value={@media_editor.editing_translation["alt"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Caption") %></label> <label><%= dgettext("ui", "Caption") %></label>
<textarea class="post-editor-textarea ui-textarea" name="media_translation[caption]" rows="3"><%= @media_editor.editing_translation["caption"] %></textarea> <textarea class="post-editor-textarea ui-textarea" name="media_translation[caption]" rows="3"><%= @media_editor.editing_translation["caption"] %></textarea>
</div> </div>

View File

@@ -1,14 +1,14 @@
<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-view ui-editor-shell flex h-full min-h-0 flex-col p-4" 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 class="menu-editor-header flex shrink-0 items-start justify-between gap-3">
<div> <div class="ui-field-stack">
<h2><%= @menu_editor.title %></h2> <h2><%= @menu_editor.title %></h2>
<p><%= @menu_editor.description %></p> <p><%= @menu_editor.description %></p>
</div> </div>
</div> </div>
<div class="menu-editor-main min-h-0 flex-1 overflow-hidden"> <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-tree-wrap ui-section-card 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}> <div class="menu-editor-toolbar ui-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")}> <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> <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7 2h2v5h5v2H9v5H7V9H2V7h5V2z" /></svg>
</button> </button>

View File

@@ -1,7 +1,7 @@
<div class="post-editor editor flex h-full min-h-0 flex-col" data-testid="post-editor"> <div class="post-editor editor ui-editor-shell flex h-full min-h-0 flex-col" data-testid="post-editor">
<div class="editor-header flex shrink-0 items-start justify-between gap-3"> <div class="editor-header ui-editor-header flex shrink-0 items-start justify-between gap-3">
<div class="editor-tabs flex min-w-0 flex-1 overflow-hidden"> <div class="editor-tabs flex min-w-0 flex-1 overflow-hidden">
<div class={["editor-tab 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")]}> <div class={["editor-tab ui-tab ui-tab-active ui-editor-tab-current active inline-flex max-w-full items-center gap-2 overflow-hidden px-3 py-2", if(@post_editor.dirty?, do: "dirty")]}>
<span class="editor-tab-title truncate" data-testid="editor-title"><%= @post_editor.display_title %></span> <span class="editor-tab-title truncate" data-testid="editor-title"><%= @post_editor.display_title %></span>
<%= if @post_editor.dirty? do %> <%= if @post_editor.dirty? do %>
<span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span> <span class="editor-tab-dirty" title={dgettext("ui", "Unsaved")}>●</span>
@@ -9,7 +9,7 @@
</div> </div>
</div> </div>
<div class="editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="editor-actions ui-editor-actions flex flex-wrap items-center justify-end gap-2">
<span class={["status-badge", "ui-badge", "status-#{@post_editor.status}"]} data-testid="post-status-badge"> <span class={["status-badge", "ui-badge", "status-#{@post_editor.status}"]} data-testid="post-status-badge">
<%= post_status_label(@post_editor.status) %> <%= post_status_label(@post_editor.status) %>
</span> </span>
@@ -29,9 +29,9 @@
</button> </button>
<%= if @post_editor.quick_actions_open? do %> <%= if @post_editor.quick_actions_open? do %>
<div class="quick-actions-menu absolute right-0 top-full z-10 mt-2 flex min-w-72 flex-col"> <div class="quick-actions-menu ui-dropdown-menu absolute right-0 top-full z-10 mt-2 flex min-w-72 flex-col">
<button <button
class="quick-action-item flex items-start gap-3 text-left" class="quick-action-item ui-dropdown-item flex items-start gap-3 text-left"
data-testid="editor-toolbar-overlay-button" data-testid="editor-toolbar-overlay-button"
type="button" type="button"
phx-click="open_overlay" phx-click="open_overlay"
@@ -48,7 +48,7 @@
<div class="quick-actions-divider"></div> <div class="quick-actions-divider"></div>
<button <button
class="quick-action-item flex items-start gap-3 text-left" class="quick-action-item ui-dropdown-item flex items-start gap-3 text-left"
data-testid="editor-toolbar-overlay-button" data-testid="editor-toolbar-overlay-button"
type="button" type="button"
phx-click="open_overlay" phx-click="open_overlay"
@@ -83,7 +83,7 @@
</div> </div>
</div> </div>
<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}> <form class="post-editor-form editor-content flex min-h-0 flex-1 flex-col gap-4 overflow-auto p-4" 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"> <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}> <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 class="metadata-toggle-chevron"><%= if @post_editor.metadata_expanded, do: "▼", else: "▶" %></span>
@@ -113,12 +113,12 @@
<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-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-meta flex min-w-0 flex-col gap-4">
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Title") %></label> <label><%= dgettext("ui", "Title") %></label>
<input class="post-editor-input ui-input" type="text" name="post_editor[title]" value={@post_editor.form["title"]} /> <input class="post-editor-input ui-input" type="text" name="post_editor[title]" value={@post_editor.form["title"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Tags") %></label> <label><%= dgettext("ui", "Tags") %></label>
<div class="tag-input-container relative"> <div class="tag-input-container relative">
<input type="hidden" name="post_editor[tags]" value={@post_editor.form["tags"]} /> <input type="hidden" name="post_editor[tags]" value={@post_editor.form["tags"]} />
@@ -162,12 +162,12 @@
</div> </div>
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Author") %></label> <label><%= dgettext("ui", "Author") %></label>
<input class="post-editor-input ui-input" type="text" name="post_editor[author]" value={@post_editor.form["author"]} /> <input class="post-editor-input ui-input" type="text" name="post_editor[author]" value={@post_editor.form["author"]} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Language") %></label> <label><%= dgettext("ui", "Language") %></label>
<div class="editor-language-row flex items-center gap-2"> <div class="editor-language-row flex items-center gap-2">
<select class="post-editor-input ui-input" name="post_editor[language]"> <select class="post-editor-input ui-input" name="post_editor[language]">
@@ -189,7 +189,7 @@
</div> </div>
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label class="editor-checkbox-label"> <label class="editor-checkbox-label">
<input type="hidden" name="post_editor[do_not_translate]" value="false" /> <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"]} /> <input type="checkbox" name="post_editor[do_not_translate]" value="true" checked={@post_editor.form["do_not_translate"]} />
@@ -197,13 +197,13 @@
</label> </label>
</div> </div>
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row ui-field-grid-2 grid gap-4 md:grid-cols-2">
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Slug") %></label> <label><%= dgettext("ui", "Slug") %></label>
<input class="post-editor-input ui-input is-readonly ui-input-readonly" type="text" readonly value={@post_editor.slug} /> <input class="post-editor-input ui-input is-readonly ui-input-readonly" type="text" readonly value={@post_editor.slug} />
</div> </div>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Categories") %></label> <label><%= dgettext("ui", "Categories") %></label>
<div class="tag-input-container relative"> <div class="tag-input-container relative">
<input type="hidden" name="post_editor[categories]" value={@post_editor.form["categories"]} /> <input type="hidden" name="post_editor[categories]" value={@post_editor.form["categories"]} />
@@ -246,7 +246,7 @@
</div> </div>
<%= if @post_editor.show_template_selector? do %> <%= if @post_editor.show_template_selector? do %>
<div class="editor-field flex flex-col gap-1.5"> <div class="editor-field ui-field-stack flex flex-col gap-1.5">
<label><%= dgettext("ui", "Template") %></label> <label><%= dgettext("ui", "Template") %></label>
<select class="post-editor-input ui-input" name="post_editor[template_slug]"> <select class="post-editor-input ui-input" name="post_editor[template_slug]">
<option value=""><%= dgettext("ui", "Default") %></option> <option value=""><%= dgettext("ui", "Default") %></option>
@@ -321,12 +321,12 @@
</div> </div>
<div class="editor-body flex min-h-0 flex-1 flex-col overflow-hidden"> <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 ui-toolbar flex items-center gap-3">
<div class="editor-toolbar-left flex items-center gap-2"> <div class="editor-toolbar-left ui-toolbar-group flex items-center gap-2">
<label><%= dgettext("ui", "Content") %></label> <label><%= dgettext("ui", "Content") %></label>
</div> </div>
<div class="editor-toolbar-center flex flex-1 justify-center"> <div class="editor-toolbar-center ui-toolbar-group flex flex-1 justify-center">
<div class="editor-mode-toggle"> <div class="editor-mode-toggle">
<%= for mode <- [:markdown, :preview] do %> <%= for mode <- [:markdown, :preview] do %>
<button <button
@@ -342,7 +342,7 @@
</div> </div>
</div> </div>
<div class="editor-toolbar-right flex items-center gap-2"> <div class="editor-toolbar-right ui-toolbar-group flex items-center gap-2">
<%= if @post_editor.mode == :markdown do %> <%= if @post_editor.mode == :markdown do %>
<button <button
class="insert-post-link-button" class="insert-post-link-button"

View File

@@ -1,7 +1,7 @@
<div class="scripts-view-shell editor flex h-full min-h-0 flex-col" data-testid="script-editor"> <div class="scripts-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col" data-testid="script-editor">
<div class="editor-header scripts-header flex shrink-0 items-start justify-between gap-3"> <div class="editor-header scripts-header ui-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 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-tabs flex min-w-0 flex-1 overflow-hidden"><div class="editor-tab ui-tab ui-tab-active ui-editor-tab-current active inline-flex max-w-full items-center overflow-hidden px-3 py-2"><span class="editor-tab-title truncate"><%= @script_editor.title %></span></div></div>
<div class="editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="editor-actions ui-editor-actions flex flex-wrap items-center justify-end gap-2">
<span class={[ <span class={[
"status-badge", "status-badge",
"ui-badge", "ui-badge",
@@ -16,22 +16,22 @@
<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> <button class="secondary danger ui-button ui-button-secondary ui-button-danger" type="button" phx-click="delete_script_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</div> </div>
<form class="editor-content scripts-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" phx-change="change_script_editor" phx-target={@myself}> <form class="editor-content scripts-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden p-4" phx-change="change_script_editor" phx-target={@myself}>
<div class="editor-header-row scripts-meta-row grid gap-4"> <div class="editor-header-row scripts-meta-row grid gap-4">
<div class="editor-meta flex min-w-0 flex-col gap-4"> <div class="editor-meta flex min-w-0 flex-col gap-4">
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row ui-field-grid-2 grid gap-4 md:grid-cols-2">
<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 ui-field-stack 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 class="editor-field ui-field-stack flex flex-col gap-1.5"><label><%= dgettext("ui", "Slug") %></label><input class="ui-input" type="text" name="script_editor[slug]" value={@script_editor.slug} /></div>
</div> </div>
<div class="editor-field-row grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]"> <div class="editor-field-row ui-field-grid-3 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 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 ui-field-stack 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 ui-field-stack flex flex-col gap-1.5"><label><%= dgettext("ui", "Entrypoint") %></label><select class="ui-input" name="script_editor[entrypoint]"><%= for entrypoint <- @script_editor.entrypoints do %><option value={entrypoint} selected={entrypoint == @script_editor.entrypoint}><%= entrypoint %></option><% end %></select></div>
<div class="editor-field scripts-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div> <div class="editor-field scripts-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="script_editor[enabled]" checked={@script_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-body scripts-editor flex min-h-0 flex-1 flex-col overflow-hidden"> <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 class="editor-toolbar scripts-toolbar ui-toolbar flex items-center gap-3"><div class="editor-toolbar-left ui-toolbar-group flex items-center gap-2"><label><%= dgettext("ui", "Content") %></label></div></div>
<div <div
id={"script-editor-monaco-shell-#{@script_editor.id}"} id={"script-editor-monaco-shell-#{@script_editor.id}"}
class="scripts-monaco monaco-editor-shell min-h-0 flex-1 overflow-hidden" class="scripts-monaco monaco-editor-shell min-h-0 flex-1 overflow-hidden"

View File

@@ -1,6 +1,6 @@
<div <div
id="settings-editor-shell" id="settings-editor-shell"
class="settings-view-shell flex h-full min-h-0 flex-col overflow-hidden" class="settings-view-shell ui-editor-shell flex h-full min-h-0 flex-col overflow-hidden"
data-testid="settings-editor" data-testid="settings-editor"
phx-hook="SettingsSectionScroll" phx-hook="SettingsSectionScroll"
data-selected-settings-section={@settings_editor.selected_section} data-selected-settings-section={@settings_editor.selected_section}
@@ -22,8 +22,8 @@
<% end %> <% end %>
<%= if @settings_editor.project_visible? do %> <%= if @settings_editor.project_visible? do %>
<div class="setting-section" id="settings-section-project"> <div class="setting-section ui-section-card" id="settings-section-project">
<div class="setting-section-header"> <div class="setting-section-header ui-field-stack">
<h3><%= dgettext("ui", "Project") %></h3> <h3><%= dgettext("ui", "Project") %></h3>
<p class="setting-section-description"><%= dgettext("ui", "Blog identity, URLs, authoring defaults, and bookmarklet setup") %></p> <p class="setting-section-description"><%= dgettext("ui", "Blog identity, URLs, authoring defaults, and bookmarklet setup") %></p>
</div> </div>
@@ -102,8 +102,8 @@
<% end %> <% end %>
<%= if @settings_editor.editor_visible? do %> <%= if @settings_editor.editor_visible? do %>
<div class="setting-section" id="settings-section-editor"> <div class="setting-section ui-section-card" id="settings-section-editor">
<div class="setting-section-header"> <div class="setting-section-header ui-field-stack">
<h3><%= dgettext("ui", "Editor") %></h3> <h3><%= dgettext("ui", "Editor") %></h3>
<p class="setting-section-description"><%= dgettext("ui", "Default editing mode and diff presentation") %></p> <p class="setting-section-description"><%= dgettext("ui", "Default editing mode and diff presentation") %></p>
</div> </div>
@@ -141,8 +141,8 @@
<% end %> <% end %>
<%= if @settings_editor.content_visible? do %> <%= if @settings_editor.content_visible? do %>
<div class="setting-section" id="settings-section-content"> <div class="setting-section ui-section-card" id="settings-section-content">
<div class="setting-section-header"><h3><%= dgettext("ui", "Content Categories") %></h3><p class="setting-section-description"><%= dgettext("ui", "Category defaults, rendering flags, and template wiring") %></p></div> <div class="setting-section-header ui-field-stack"><h3><%= dgettext("ui", "Content Categories") %></h3><p class="setting-section-description"><%= dgettext("ui", "Category defaults, rendering flags, and template wiring") %></p></div>
<div class="setting-section-content"> <div class="setting-section-content">
<table class="categories-table"> <table class="categories-table">
<thead> <thead>
@@ -209,8 +209,8 @@
<% end %> <% end %>
<%= if @settings_editor.ai_visible? do %> <%= if @settings_editor.ai_visible? do %>
<div class="setting-section" id="settings-section-ai"> <div class="setting-section ui-section-card" id="settings-section-ai">
<div class="setting-section-header"><h3><%= dgettext("ui", "AI") %></h3><p class="setting-section-description"><%= dgettext("ui", "OpenAI-compatible endpoints, model routing, airplane mode, and system prompt") %></p></div> <div class="setting-section-header ui-field-stack"><h3><%= dgettext("ui", "AI") %></h3><p class="setting-section-description"><%= dgettext("ui", "OpenAI-compatible endpoints, model routing, airplane mode, and system prompt") %></p></div>
<form class="setting-section-content" phx-change="change_settings_ai" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_ai" phx-target={@myself}>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Endpoint URL") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Online Endpoint URL") %></label></div>
@@ -310,8 +310,8 @@
<% end %> <% end %>
<%= if @settings_editor.technology_visible? do %> <%= if @settings_editor.technology_visible? do %>
<div class="setting-section" id="settings-section-technology"> <div class="setting-section ui-section-card" id="settings-section-technology">
<div class="setting-section-header"><h3><%= dgettext("ui", "Technology") %></h3><p class="setting-section-description"><%= dgettext("ui", "Application-level runtime behavior and semantic indexing") %></p></div> <div class="setting-section-header ui-field-stack"><h3><%= dgettext("ui", "Technology") %></h3><p class="setting-section-description"><%= dgettext("ui", "Application-level runtime behavior and semantic indexing") %></p></div>
<form class="setting-section-content" phx-change="change_settings_project" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_project" phx-target={@myself}>
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Semantic Similarity") %></label></div> <div class="setting-info"><label class="setting-label"><%= dgettext("ui", "Semantic Similarity") %></label></div>
@@ -327,8 +327,8 @@
<% end %> <% end %>
<%= if @settings_editor.publishing_visible? do %> <%= if @settings_editor.publishing_visible? do %>
<div class="setting-section" id="settings-section-publishing"> <div class="setting-section ui-section-card" id="settings-section-publishing">
<div class="setting-section-header"><h3><%= dgettext("ui", "Publishing") %></h3><p class="setting-section-description"><%= dgettext("ui", "Deployment credentials for upload tasks") %></p></div> <div class="setting-section-header ui-field-stack"><h3><%= dgettext("ui", "Publishing") %></h3><p class="setting-section-description"><%= dgettext("ui", "Deployment credentials for upload tasks") %></p></div>
<form class="setting-section-content" phx-change="change_settings_publishing" phx-target={@myself}> <form class="setting-section-content" phx-change="change_settings_publishing" phx-target={@myself}>
<div class="setting-row"><div class="setting-info"><label class="setting-label"><%= dgettext("ui", "SSH Mode") %></label></div><div class="setting-control"><select 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", "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", "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>
@@ -340,8 +340,8 @@
<% end %> <% end %>
<%= if @settings_editor.mcp_visible? do %> <%= if @settings_editor.mcp_visible? do %>
<div class="setting-section" id="settings-section-mcp"> <div class="setting-section ui-section-card" id="settings-section-mcp">
<div class="setting-section-header"><h3><%= dgettext("ui", "MCP") %></h3><p class="setting-section-description"><%= dgettext("ui", "Agent configuration files for the built-in bDS MCP server") %></p></div> <div class="setting-section-header ui-field-stack"><h3><%= dgettext("ui", "MCP") %></h3><p class="setting-section-description"><%= dgettext("ui", "Agent configuration files for the built-in bDS MCP server") %></p></div>
<div class="setting-section-content"> <div class="setting-section-content">
<%= for agent <- @settings_editor.mcp do %> <%= for agent <- @settings_editor.mcp do %>
<div class="setting-row"> <div class="setting-row">
@@ -361,8 +361,8 @@
<% end %> <% end %>
<%= if @settings_editor.data_visible? do %> <%= if @settings_editor.data_visible? do %>
<div class="setting-section" id="settings-section-data"> <div class="setting-section ui-section-card" id="settings-section-data">
<div class="setting-section-header"><h3><%= dgettext("ui", "Data Maintenance") %></h3><p class="setting-section-description"><%= dgettext("ui", "Rebuild filesystem-backed records and thumbnails") %></p></div> <div class="setting-section-header ui-field-stack"><h3><%= dgettext("ui", "Data Maintenance") %></h3><p class="setting-section-description"><%= dgettext("ui", "Rebuild filesystem-backed records and thumbnails") %></p></div>
<div class="setting-actions"> <div class="setting-actions">
<button class="secondary 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_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_media_from_files"><%= dgettext("ui", "Rebuild Media From Files") %></button>

View File

@@ -1,7 +1,7 @@
<div class="templates-view-shell editor flex h-full min-h-0 flex-col" data-testid="template-editor"> <div class="templates-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col" data-testid="template-editor">
<div class="editor-header templates-header flex shrink-0 items-start justify-between gap-3"> <div class="editor-header templates-header ui-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 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-tabs flex min-w-0 flex-1 overflow-hidden"><div class="editor-tab ui-tab ui-tab-active ui-editor-tab-current active inline-flex max-w-full items-center overflow-hidden px-3 py-2"><span class="editor-tab-title truncate"><%= @template_editor.title %></span></div></div>
<div class="editor-actions flex flex-wrap items-center justify-end gap-2"> <div class="editor-actions ui-editor-actions flex flex-wrap items-center justify-end gap-2">
<span class={[ <span class={[
"status-badge", "status-badge",
"ui-badge", "ui-badge",
@@ -15,21 +15,21 @@
<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> <button class="secondary danger ui-button ui-button-secondary ui-button-danger" type="button" phx-click="delete_template_editor" phx-target={@myself}><%= dgettext("ui", "Delete") %></button>
</div> </div>
</div> </div>
<form class="editor-content templates-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" phx-change="change_template_editor" phx-target={@myself}> <form class="editor-content templates-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden p-4" phx-change="change_template_editor" phx-target={@myself}>
<div class="editor-header-row templates-meta-row grid gap-4"> <div class="editor-header-row templates-meta-row grid gap-4">
<div class="editor-meta flex min-w-0 flex-col gap-4"> <div class="editor-meta flex min-w-0 flex-col gap-4">
<div class="editor-field-row grid gap-4 md:grid-cols-2"> <div class="editor-field-row ui-field-grid-2 grid gap-4 md:grid-cols-2">
<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 ui-field-stack 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 class="editor-field ui-field-stack flex flex-col gap-1.5"><label><%= dgettext("ui", "Slug") %></label><input class="ui-input" type="text" name="template_editor[slug]" value={@template_editor.slug} /></div>
</div> </div>
<div class="editor-field-row grid gap-4 md:grid-cols-[minmax(0,1fr)_auto]"> <div class="editor-field-row ui-field-grid-3 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 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 ui-field-stack flex flex-col gap-1.5"><label><%= dgettext("ui", "Kind") %></label><select class="ui-input" name="template_editor[kind]"><option value="post" selected={@template_editor.kind == :post or @template_editor.kind == "post"}>post</option><option value="list" selected={@template_editor.kind == :list or @template_editor.kind == "list"}>list</option><option value="not-found" selected={@template_editor.kind == :"not-found" or @template_editor.kind == "not-found"}>not-found</option><option value="partial" selected={@template_editor.kind == :partial or @template_editor.kind == "partial"}>partial</option></select></div>
<div class="editor-field templates-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div> <div class="editor-field templates-enabled-field flex flex-col justify-end gap-1.5"><label><input type="checkbox" name="template_editor[enabled]" checked={@template_editor.enabled} /> <%= dgettext("ui", "Enabled") %></label></div>
</div> </div>
</div> </div>
</div> </div>
<div class="editor-body templates-editor flex min-h-0 flex-1 flex-col overflow-hidden"> <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 class="editor-toolbar templates-toolbar ui-toolbar flex items-center gap-3"><div class="editor-toolbar-left ui-toolbar-group flex items-center gap-2"><label><%= dgettext("ui", "Content") %></label></div></div>
<div <div
id={"template-editor-monaco-shell-#{@template_editor.id}"} id={"template-editor-monaco-shell-#{@template_editor.id}"}
class="templates-monaco monaco-editor-shell min-h-0 flex-1 overflow-hidden" class="templates-monaco monaco-editor-shell min-h-0 flex-1 overflow-hidden"

View File

@@ -292,27 +292,27 @@ defmodule BDS.Desktop.ShellLiveTest do
settings_html = render_component(&BDS.Desktop.ShellLive.SettingsEditor.render/1, phase3_settings_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()) tags_html = render_component(&BDS.Desktop.ShellLive.TagsEditor.render/1, phase3_tags_editor_assigns())
assert post_html =~ "post-editor editor flex h-full min-h-0 flex-col" assert post_html =~ "post-editor editor ui-editor-shell flex h-full min-h-0 flex-col"
assert post_html =~ "editor-header flex shrink-0 items-start justify-between gap-3" assert post_html =~ "editor-header ui-editor-header flex shrink-0 items-start justify-between gap-3"
assert post_html =~ "editor-field flex flex-col gap-1.5" assert post_html =~ "editor-field ui-field-stack flex flex-col gap-1.5"
assert post_html =~ "editor-toolbar flex items-center gap-3" assert post_html =~ "editor-toolbar ui-toolbar flex items-center gap-3"
assert media_html =~ "media-editor editor flex h-full min-h-0 flex-col" assert media_html =~ "media-editor editor ui-editor-shell flex h-full min-h-0 flex-col"
assert media_html =~ "editor-content media-editor grid min-h-0 flex-1 gap-4" assert media_html =~ "editor-content media-editor grid min-h-0 flex-1 gap-4 overflow-auto p-4"
assert script_html =~ "scripts-view-shell editor flex h-full min-h-0 flex-col" assert script_html =~ "scripts-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col"
assert script_html =~ "editor-content scripts-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" assert script_html =~ "editor-content scripts-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden p-4"
assert template_html =~ "templates-view-shell editor flex h-full min-h-0 flex-col" assert template_html =~ "templates-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col"
assert template_html =~ "editor-content templates-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden" assert template_html =~ "editor-content templates-view flex min-h-0 flex-1 flex-col gap-4 overflow-hidden p-4"
assert chat_html =~ "chat-panel flex h-full min-h-0 flex-col" assert chat_html =~ "chat-panel ui-editor-shell flex h-full min-h-0 flex-col"
assert chat_html =~ "chat-panel-header flex shrink-0 items-center justify-between gap-3" assert chat_html =~ "chat-panel-header flex shrink-0 items-center justify-between gap-3"
assert menu_html =~ "menu-editor-view flex h-full min-h-0 flex-col" assert menu_html =~ "menu-editor-view ui-editor-shell flex h-full min-h-0 flex-col p-4"
assert menu_html =~ "menu-editor-toolbar flex flex-wrap items-center gap-2" assert menu_html =~ "menu-editor-toolbar ui-toolbar flex flex-wrap items-center gap-2"
assert settings_html =~ "settings-view-shell flex h-full min-h-0 flex-col overflow-hidden" assert settings_html =~ "settings-view-shell ui-editor-shell flex h-full min-h-0 flex-col overflow-hidden"
assert settings_html =~ "settings-header flex shrink-0 items-center justify-between gap-3" assert settings_html =~ "settings-header flex shrink-0 items-center justify-between gap-3"
assert tags_html =~ "tags-view-shell flex h-full min-h-0 flex-col overflow-hidden" assert tags_html =~ "tags-view-shell flex h-full min-h-0 flex-col overflow-hidden"
@@ -377,6 +377,34 @@ defmodule BDS.Desktop.ShellLiveTest do
assert panel_html =~ ~s(class="panel-entry ui-panel-entry panel-empty-state ui-empty-state) assert panel_html =~ ~s(class="panel-entry ui-panel-entry panel-empty-state ui-empty-state)
end end
@tag :phase5
test "phase 5 desktop-specific surfaces keep shell, media, menu, and chat contracts" do
conn = Plug.Conn.put_private(build_conn(), :phoenix_endpoint, BDS.Desktop.Endpoint)
{:ok, _view, shell_html} = live_isolated(conn, BDS.Desktop.ShellLive)
media_html = render_component(&BDS.Desktop.ShellLive.MediaEditor.render/1, phase3_media_editor_assigns())
chat_html = render_component(&BDS.Desktop.ShellLive.ChatEditor.render/1, phase3_chat_editor_assigns())
menu_html = render_component(&BDS.Desktop.ShellLive.MenuEditor.render/1, phase3_menu_editor_assigns())
assert shell_html =~ ~s(class="assistant-sidebar-context flex shrink-0 flex-col gap-2")
assert shell_html =~ ~s(class="assistant-sidebar-prompt min-h-[8rem] w-full resize-y")
assert shell_html =~ ~s(class="assistant-sidebar-start-button ui-button ui-button-primary")
assert shell_html =~ ~s(class="assistant-sidebar-welcome min-h-0 flex-1 overflow-auto")
assert media_html =~ "class=\"editor-content media-editor grid min-h-0 flex-1 gap-4 overflow-auto p-4 xl:grid-cols-[minmax(320px,1fr)_minmax(0,1.2fr)]\""
assert media_html =~ "class=\"media-preview flex min-h-[16rem] items-center justify-center\""
assert media_html =~ ~s(class="media-details min-w-0")
assert chat_html =~ ~s(class="chat-panel ui-editor-shell flex h-full min-h-0 flex-col")
assert chat_html =~ ~s(class="chat-model-selector-button chat-model-selector-inline ui-button ui-button-secondary inline-flex items-center gap-2")
assert chat_html =~ ~s(class="chat-input-container ui-field-stack flex shrink-0 flex-col gap-3")
assert chat_html =~ ~s(class="chat-input-wrapper flex items-end gap-2")
assert menu_html =~ ~s(class="menu-editor-view ui-editor-shell flex h-full min-h-0 flex-col p-4")
assert menu_html =~ ~s(class="menu-editor-toolbar ui-toolbar flex flex-wrap items-center gap-2")
assert menu_html =~ ~s(class="menu-editor-empty flex min-h-0 flex-1 items-center justify-center")
end
alias BDS.Persistence alias BDS.Persistence
alias BDS.AI alias BDS.AI
alias BDS.CliSync.Watcher alias BDS.CliSync.Watcher
@@ -3195,7 +3223,7 @@ defmodule BDS.Desktop.ShellLiveTest do
"subtitle" => "published" "subtitle" => "published"
}) })
assert published_script_html =~ ~s(class="scripts-view-shell editor flex h-full min-h-0 flex-col") assert published_script_html =~ ~s(class="scripts-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col")
assert published_script_html =~ ~s(data-testid="script-editor") assert published_script_html =~ ~s(data-testid="script-editor")
assert published_script_html =~ ~s(data-testid="script-status-badge") assert published_script_html =~ ~s(data-testid="script-status-badge")
assert published_script_html =~ ~s(class="status-badge ui-badge status-published") assert published_script_html =~ ~s(class="status-badge ui-badge status-published")
@@ -3216,7 +3244,7 @@ defmodule BDS.Desktop.ShellLiveTest do
"subtitle" => "published" "subtitle" => "published"
}) })
assert published_template_html =~ ~s(class="templates-view-shell editor flex h-full min-h-0 flex-col") assert published_template_html =~ ~s(class="templates-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col")
assert published_template_html =~ ~s(data-testid="template-editor") assert published_template_html =~ ~s(data-testid="template-editor")
assert published_template_html =~ ~s(data-testid="template-status-badge") assert published_template_html =~ ~s(data-testid="template-status-badge")
assert published_template_html =~ ~s(class="status-badge ui-badge status-published") assert published_template_html =~ ~s(class="status-badge ui-badge status-published")
@@ -3410,7 +3438,7 @@ defmodule BDS.Desktop.ShellLiveTest do
}) })
assert html =~ assert html =~
"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)]\"" "class=\"editor-content media-editor grid min-h-0 flex-1 gap-4 overflow-auto p-4 xl:grid-cols-[minmax(320px,1fr)_minmax(0,1.2fr)]\""
assert html =~ ~s(class="quick-actions-wrapper relative") assert html =~ ~s(class="quick-actions-wrapper relative")
refute html =~ ~s(class="media-editor-form") refute html =~ ~s(class="media-editor-form")
@@ -3523,8 +3551,8 @@ defmodule BDS.Desktop.ShellLiveTest do
"subtitle" => "Project settings" "subtitle" => "Project settings"
}) })
assert settings_html =~ ~s(class="settings-view-shell flex h-full min-h-0 flex-col overflow-hidden") assert settings_html =~ ~s(class="settings-view-shell ui-editor-shell flex h-full min-h-0 flex-col overflow-hidden")
assert settings_html =~ ~s(class="setting-section") assert settings_html =~ ~s(class="setting-section ui-section-card")
refute settings_html =~ "Desktop workbench content routed through the Elixir shell." refute settings_html =~ "Desktop workbench content routed through the Elixir shell."
tags_html = tags_html =
@@ -3559,7 +3587,7 @@ defmodule BDS.Desktop.ShellLiveTest do
"subtitle" => script.slug "subtitle" => script.slug
}) })
assert script_html =~ ~s(class="scripts-view-shell editor flex h-full min-h-0 flex-col") assert script_html =~ ~s(class="scripts-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col")
assert script_html =~ "scripts-monaco" assert script_html =~ "scripts-monaco"
assert script_html =~ ~s(data-monaco-language="lua") assert script_html =~ ~s(data-monaco-language="lua")
assert script_html =~ ~s(data-monaco-word-wrap="on") assert script_html =~ ~s(data-monaco-word-wrap="on")
@@ -3574,7 +3602,7 @@ defmodule BDS.Desktop.ShellLiveTest do
"subtitle" => template.slug "subtitle" => template.slug
}) })
assert template_html =~ ~s(class="templates-view-shell editor flex h-full min-h-0 flex-col") assert template_html =~ ~s(class="templates-view-shell editor ui-editor-shell flex h-full min-h-0 flex-col")
assert template_html =~ "templates-monaco" assert template_html =~ "templates-monaco"
assert template_html =~ ~s(data-monaco-language="liquid") assert template_html =~ ~s(data-monaco-language="liquid")
assert template_html =~ ~s(data-monaco-word-wrap="on") assert template_html =~ ~s(data-monaco-word-wrap="on")
@@ -3589,8 +3617,8 @@ defmodule BDS.Desktop.ShellLiveTest do
"subtitle" => conversation.model || "chat" "subtitle" => conversation.model || "chat"
}) })
assert chat_html =~ ~s(class="chat-panel flex h-full min-h-0 flex-col") assert chat_html =~ ~s(class="chat-panel ui-editor-shell flex h-full min-h-0 flex-col")
assert chat_html =~ ~s(class="chat-input-container flex shrink-0 flex-col gap-3") assert chat_html =~ ~s(class="chat-input-container ui-field-stack flex shrink-0 flex-col gap-3")
refute chat_html =~ "Desktop workbench content routed through the Elixir shell." refute chat_html =~ "Desktop workbench content routed through the Elixir shell."
end end
@@ -3658,7 +3686,7 @@ defmodule BDS.Desktop.ShellLiveTest do
|> element("[data-testid='chat-model-selector-button']") |> element("[data-testid='chat-model-selector-button']")
|> render_click() |> render_click()
assert selector_html =~ ~s(class="chat-model-selector-menu absolute right-0 top-full z-10 mt-2 flex min-w-56 flex-col") assert selector_html =~ ~s(class="chat-model-selector-menu ui-dropdown-menu absolute right-0 top-full z-10 mt-2 flex min-w-56 flex-col")
assert selector_html =~ ~s(data-testid="chat-model-selector-option") assert selector_html =~ ~s(data-testid="chat-model-selector-option")
assert selector_html =~ "llama-current" assert selector_html =~ "llama-current"

View File

@@ -152,6 +152,138 @@ defmodule BDS.UI.ShellTest do
assert css =~ ".ui-badge {" assert css =~ ".ui-badge {"
assert css =~ ".ui-panel-entry {" assert css =~ ".ui-panel-entry {"
assert css =~ ".ui-empty-state {" assert css =~ ".ui-empty-state {"
assert css =~ ".ui-editor-shell {"
assert css =~ ".ui-editor-header {"
assert css =~ ".ui-editor-tab-current {"
assert css =~ ".ui-editor-actions {"
assert css =~ ".ui-toolbar {"
assert css =~ ".ui-toolbar-group {"
assert css =~ ".ui-field-stack {"
assert css =~ ".ui-field-grid-2 {"
assert css =~ ".ui-field-grid-3 {"
assert css =~ ".ui-dropdown-menu {"
assert css =~ ".ui-dropdown-item {"
assert css =~ ".ui-section-card {"
end
test "phase 3 templates use shared shell and form primitives for common layout" do
post_template =
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/post_editor_html/post_editor.html.heex")
media_template =
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/media_editor_html/media_editor.html.heex")
script_template =
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/script_editor_html/script_editor.html.heex")
template_template =
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/template_editor_html/template_editor.html.heex")
chat_template =
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/chat_editor_html/chat_editor.html.heex")
menu_template =
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/menu_editor_html/menu_editor.html.heex")
settings_template =
File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/settings_editor_html/settings_editor.html.heex")
assert post_template =~ "ui-editor-shell"
assert post_template =~ "ui-editor-header"
assert post_template =~ "ui-editor-tab-current"
assert post_template =~ "ui-editor-actions"
assert post_template =~ "ui-field-stack"
assert post_template =~ "ui-field-grid-2"
assert post_template =~ "ui-toolbar"
assert post_template =~ "ui-toolbar-group"
assert post_template =~ "ui-dropdown-menu"
assert post_template =~ "ui-dropdown-item"
assert media_template =~ "ui-editor-shell"
assert media_template =~ "ui-editor-header"
assert media_template =~ "ui-editor-tab-current"
assert media_template =~ "ui-editor-actions"
assert media_template =~ "ui-field-stack"
assert media_template =~ "ui-field-grid-2"
assert media_template =~ "ui-dropdown-menu"
assert media_template =~ "ui-dropdown-item"
assert script_template =~ "ui-editor-shell"
assert script_template =~ "ui-editor-header"
assert script_template =~ "ui-editor-tab-current"
assert script_template =~ "ui-editor-actions"
assert script_template =~ "ui-field-stack"
assert template_template =~ "ui-editor-shell"
assert template_template =~ "ui-editor-header"
assert template_template =~ "ui-editor-tab-current"
assert template_template =~ "ui-editor-actions"
assert template_template =~ "ui-field-stack"
assert chat_template =~ "ui-editor-shell"
assert chat_template =~ "ui-section-card"
assert chat_template =~ "ui-dropdown-menu"
assert chat_template =~ "ui-dropdown-item"
assert chat_template =~ "ui-field-stack"
assert menu_template =~ "ui-editor-shell"
assert menu_template =~ "ui-section-card"
assert menu_template =~ "ui-toolbar"
assert settings_template =~ "ui-editor-shell"
assert settings_template =~ "ui-field-stack"
assert settings_template =~ "ui-section-card"
end
test "phase 3 trims redundant common-case layout rules from authored css slices" do
editor_css = File.read!("/Users/gb/Projects/bDS2/assets/css/editor.css")
media_css = File.read!("/Users/gb/Projects/bDS2/assets/css/media_editor.css")
assistant_css = File.read!("/Users/gb/Projects/bDS2/assets/css/assistant.css")
menu_css = File.read!("/Users/gb/Projects/bDS2/assets/css/menu_editor.css")
refute editor_css =~ ".post-editor .editor-header,\n.scripts-view-shell.editor .editor-header,\n.templates-view-shell.editor .editor-header {\n display: flex;"
refute editor_css =~ ".post-editor .editor-actions,\n.scripts-view-shell.editor .editor-actions,\n.templates-view-shell.editor .editor-actions {\n display: flex;"
refute editor_css =~ ".post-editor .quick-actions-menu {"
refute media_css =~ "[data-testid=\"media-editor\"] .editor-header {"
refute media_css =~ "[data-testid=\"media-editor\"] .editor-actions {"
refute media_css =~ "[data-testid=\"media-editor\"] .quick-actions-menu {"
refute assistant_css =~ ".chat-panel-header {\n display: flex;"
refute assistant_css =~ ".chat-panel .chat-input-wrapper {\n display: flex;"
refute menu_css =~ ".menu-editor-view {\n padding: 1rem;"
refute menu_css =~ ".menu-editor-toolbar {\n display: flex;"
end
test "phase 5 desktop-specific surfaces stay in source modules with responsive behavior" do
css = css_source()
app_js = File.read!("/Users/gb/Projects/bDS2/assets/js/app.js")
assert css =~ ".ai-suggestions-modal-backdrop"
assert css =~ ".gallery-overlay"
assert css =~ ".lightbox-overlay"
assert css =~ ".menu-editor-row.is-dragging"
assert css =~ ".menu-editor-row.is-drop-before::before"
assert css =~ ".menu-editor-row.is-drop-after::after"
assert css =~ ".menu-editor-row.is-drop-inside"
assert app_js =~ "MenuEditorTree"
assert app_js =~ "classList.add(\"is-dragging\")"
assert app_js =~ "pushEvent(\"menu_editor_drop_item\""
assert css =~ ".media-preview {"
assert css =~ ".media-preview-image img {"
assert css =~ "object-fit: contain;"
assert css =~ ".media-details {"
assert css =~ "width: 320px;"
assert css =~ ".assistant-sidebar-context {"
assert css =~ ".assistant-sidebar-message {"
assert css =~ ".chat-panel .chat-input-container"
assert css =~ ".chat-model-selector-menu"
assert css =~ "@media (max-width: 720px) {\n .chat-panel-header {\n align-items: stretch;\n flex-direction: column;"
assert css =~ ".chat-model-selector-wrap {\n width: 100%;"
assert css =~ ".chat-panel .chat-model-selector-button.chat-model-selector-inline {\n justify-content: space-between;\n width: 100%;"
assert css =~ ".chat-panel .chat-input-container {\n padding: 8px 12px;"
end end
test "tailwind source keeps theme tokens and shared component primitives" do test "tailwind source keeps theme tokens and shared component primitives" do