diff --git a/static/css/components/recipe-modal.css b/static/css/components/recipe-modal.css
index 2be60cfd..23be8846 100644
--- a/static/css/components/recipe-modal.css
+++ b/static/css/components/recipe-modal.css
@@ -4,15 +4,20 @@
justify-content: flex-start;
align-items: flex-start;
border-bottom: 1px solid var(--lora-border);
- padding-bottom: 10px;
- margin-bottom: 10px;
+ padding-bottom: var(--space-2);
+ margin-bottom: var(--space-3);
+ position: relative;
}
.recipe-modal-header h2 {
- font-size: 1.4em; /* Reduced from default h2 size */
- line-height: 1.3;
- margin: 0;
- max-height: 2.6em; /* Limit to 2 lines */
+ margin: 0 0 var(--space-1);
+ padding: var(--space-1);
+ border-radius: var(--border-radius-xs);
+ font-size: 1.5em;
+ font-weight: 600;
+ line-height: 1.2;
+ color: var(--text-color);
+ max-height: 2.8em;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
@@ -127,7 +132,7 @@
/* Recipe Tags styles */
.recipe-tags-container {
position: relative;
- margin-top: 6px;
+ margin-top: 0;
margin-bottom: 10px;
}
@@ -225,6 +230,62 @@
overflow: hidden;
}
+/* Recipe Header Actions */
+.recipe-header-actions {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2);
+ flex-wrap: wrap;
+ width: 100%;
+ margin-bottom: var(--space-1);
+ flex-shrink: 0;
+ min-height: 0;
+}
+
+.recipe-header-actions:empty {
+ display: none;
+}
+
+.recipe-source-url-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 12px;
+ background: rgba(0, 0, 0, 0.03);
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ border-radius: var(--border-radius-sm);
+ color: var(--text-color);
+ cursor: pointer;
+ font-weight: 500;
+ font-size: 0.9em;
+ transition: all 0.2s;
+ white-space: nowrap;
+}
+
+[data-theme="dark"] .recipe-source-url-btn {
+ background: rgba(255, 255, 255, 0.03);
+ border: 1px solid var(--lora-border);
+}
+
+.recipe-source-url-btn:hover {
+ background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.1);
+ border-color: var(--lora-accent);
+ transform: translateY(-1px);
+}
+
+.recipe-source-url-btn i {
+ font-size: 14px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+@media (max-height: 860px) {
+ .recipe-header-actions {
+ padding-bottom: 4px;
+ }
+}
+
/* Top Section: Preview and Gen Params */
.recipe-top-section {
display: grid;
@@ -1083,13 +1144,13 @@
}
.recipe-modal-header {
- padding-bottom: 6px;
- margin-bottom: 8px;
+ padding-bottom: var(--space-1);
+ margin-bottom: var(--space-2);
}
.recipe-modal-header h2 {
- font-size: 1.25em;
- max-height: 2.5em;
+ font-size: 1.3em;
+ max-height: 2.4em;
}
.recipe-tags-container {
diff --git a/static/js/components/RecipeModal.js b/static/js/components/RecipeModal.js
index b461d236..02f5528d 100644
--- a/static/js/components/RecipeModal.js
+++ b/static/js/components/RecipeModal.js
@@ -383,6 +383,7 @@ class RecipeModal {
this.syncGenerationParams(hydratedRecipe.gen_params);
this.syncResourcesSection(hydratedRecipe);
+ this.syncSourceUrlAction();
// Show the modal
modalManager.showModal('recipeModal');
@@ -515,6 +516,7 @@ class RecipeModal {
} else {
this.updateSourceUrlDisplay(this.currentRecipe.source_path || '');
}
+ this.syncSourceUrlAction();
}
getPreviewMediaUrl(recipe = {}) {
@@ -582,6 +584,30 @@ class RecipeModal {
}
}
+ syncSourceUrlAction() {
+ const actionsContainer = document.getElementById('recipeHeaderActions');
+ if (!actionsContainer) {
+ return;
+ }
+
+ actionsContainer.innerHTML = '';
+
+ const sourcePath = this.currentRecipe?.source_path || '';
+ const isValidUrl = sourcePath.startsWith('http://') || sourcePath.startsWith('https://');
+ if (!isValidUrl) {
+ return;
+ }
+
+ const btn = document.createElement('button');
+ btn.className = 'recipe-source-url-btn';
+ btn.title = sourcePath;
+ btn.innerHTML = ' Open Source URL';
+ btn.addEventListener('click', () => {
+ window.open(sourcePath, '_blank');
+ });
+ actionsContainer.appendChild(btn);
+ }
+
syncTagsDisplay(tags) {
const tagsContainer = document.getElementById('recipeTagsCompact');
if (!tagsContainer) {
@@ -1316,6 +1342,7 @@ class RecipeModal {
// Update source URL in the UI
this.commitField('source_path');
this.updateSourceUrlDisplay(newSourceUrl, { forceInputSync: true });
+ this.syncSourceUrlAction();
// Update the current recipe object
this.currentRecipe.source_path = newSourceUrl;
diff --git a/templates/components/recipe_modal.html b/templates/components/recipe_modal.html
index 74ce975c..41327356 100644
--- a/templates/components/recipe_modal.html
+++ b/templates/components/recipe_modal.html
@@ -4,6 +4,8 @@