diff --git a/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py b/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py index 1522c455..723aa3e1 100644 --- a/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py +++ b/backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py @@ -257,12 +257,16 @@ class WordPressAdapter(BaseAdapter): featured_image_url = image_url logger.info(f"[WordPressAdapter._publish_via_api_key] 🖼️ Featured image: {image_url[:80]}...") elif image.image_type == 'in_article' and image_url: + is_featured = False # In-article images are never featured gallery_images.append({ 'url': image_url, 'alt': getattr(image, 'alt', '') or '', - 'caption': getattr(image, 'caption', '') or '' + 'caption': getattr(image, 'caption', '') or '', + 'prompt': getattr(image, 'prompt', '') or '', + 'position': getattr(image, 'position', 0), + 'is_featured': is_featured }) - logger.info(f"[WordPressAdapter._publish_via_api_key] 🖼️ Gallery image {len(gallery_images)}") + logger.info(f"[WordPressAdapter._publish_via_api_key] 🖼️ Gallery image {len(gallery_images)} (pos={getattr(image, 'position', 0)}, prompt={bool(getattr(image, 'prompt', ''))})") except Exception as e: logger.warning(f"[WordPressAdapter._publish_via_api_key] ⚠️ Could not load images: {e}") diff --git a/backend/igny8_core/settings.py b/backend/igny8_core/settings.py index 59a4e1df..e774cab0 100644 --- a/backend/igny8_core/settings.py +++ b/backend/igny8_core/settings.py @@ -785,7 +785,7 @@ UNFOLD = { {"title": "System AI Settings", "icon": "tune", "link": lambda request: "/admin/system/systemaisettings/"}, {"title": "AI Models", "icon": "model_training", "link": lambda request: "/admin/billing/aimodelconfig/"}, {"title": "Credit Costs by Function", "icon": "calculate", "link": lambda request: "/admin/billing/creditcostconfig/"}, - {"title": "Account-Specific AI Settings", "icon": "account_circle", "link": lambda request: "/admin/system/aisettings/"}, + {"title": "Billing Configuration", "icon": "payments", "link": lambda request: "/admin/billing/billingconfiguration/"}, {"title": "AI Task Logs", "icon": "history", "link": lambda request: "/admin/ai/aitasklog/"}, ], }, @@ -814,7 +814,6 @@ UNFOLD = { {"title": "Module Settings", "icon": "view_module", "link": lambda request: "/admin/system/globalmodulesettings/"}, {"title": "Author Profiles", "icon": "person_outline", "link": lambda request: "/admin/system/globalauthorprofile/"}, {"title": "Strategies", "icon": "strategy", "link": lambda request: "/admin/system/globalstrategy/"}, - {"title": "Billing Configuration", "icon": "payments", "link": lambda request: "/admin/billing/billingconfiguration/"}, ], }, # System Configuration @@ -823,8 +822,7 @@ UNFOLD = { "icon": "tune", "collapsible": True, "items": [ - {"title": "System Settings", "icon": "settings", "link": lambda request: "/admin/system/systemsettings/"}, - {"title": "Account Settings", "icon": "account_circle", "link": lambda request: "/admin/system/accountsettings/"}, + {"title": "Account Settings (All Settings)", "icon": "account_circle", "link": lambda request: "/admin/system/accountsettings/"}, {"title": "User Settings", "icon": "person_search", "link": lambda request: "/admin/system/usersettings/"}, {"title": "Module Settings", "icon": "view_module", "link": lambda request: "/admin/system/modulesettings/"}, ], diff --git a/frontend/src/templates/ContentViewTemplate.tsx b/frontend/src/templates/ContentViewTemplate.tsx index 4fcabe0d..01d12e1b 100644 --- a/frontend/src/templates/ContentViewTemplate.tsx +++ b/frontend/src/templates/ContentViewTemplate.tsx @@ -458,34 +458,70 @@ const ContentSectionBlock = ({ {/* Content layout with images */}
- {/* Content before H3 */} - {beforeH3 && ( -
-
-
- )} - - {/* Image section - layout depends on aspect ratio and alignment */} - {hasImage && ( -
-
+ {/* Square images (left/right aligned) - content and image in same row */} + {aspectRatio === 'square' && (imageAlign === 'left' || imageAlign === 'right') && hasImage ? ( +
+ {/* Image side (48% width) */} +
+ + {/* Content side (48% width with auto remaining) */} +
+ {/* Content before H3 */} + {beforeH3 && ( +
+
+
+ )} + + {/* H3 and remaining content */} + {h3AndAfter && ( +
+
+
+ )} + + {/* Fallback if no H3 structure found */} + {!beforeH3 && !h3AndAfter && ( +
+
+
+ )} +
- )} - - {/* H3 and remaining content */} - {h3AndAfter && ( -
-
-
- )} - - {/* Fallback if no H3 structure found */} - {!beforeH3 && !h3AndAfter && ( -
-
-
+ ) : ( + <> + {/* Content before H3 */} + {beforeH3 && ( +
+
+
+ )} + + {/* Landscape image - full width centered */} + {hasImage && aspectRatio === 'landscape' && ( +
+
+ +
+
+ )} + + {/* H3 and remaining content */} + {h3AndAfter && ( +
+
+
+ )} + + {/* Fallback if no H3 structure found */} + {!beforeH3 && !h3AndAfter && ( +
+
+
+ )} + )}
diff --git a/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php b/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php index 0a162f48..e0316681 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php +++ b/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php @@ -3,7 +3,7 @@ * Plugin Name: IGNY8 WordPress Bridge * Plugin URI: https://igny8.com/igny8-wp-bridge * Description: Lightweight bridge plugin that connects WordPress to IGNY8 API for one-way content publishing. - * Version: 1.2.6 + * Version: 1.2.7 * Author: IGNY8 * Author URI: https://igny8.com/ * License: GPL v2 or later @@ -22,7 +22,7 @@ if (!defined('ABSPATH')) { } // Define plugin constants -define('IGNY8_BRIDGE_VERSION', '1.2.6'); +define('IGNY8_BRIDGE_VERSION', '1.2.7'); define('IGNY8_BRIDGE_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('IGNY8_BRIDGE_PLUGIN_URL', plugin_dir_url(__FILE__)); define('IGNY8_BRIDGE_PLUGIN_FILE', __FILE__); diff --git a/plugins/wordpress/source/igny8-wp-bridge/includes/template-functions.php b/plugins/wordpress/source/igny8-wp-bridge/includes/template-functions.php index 42d27c62..bdf01851 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/includes/template-functions.php +++ b/plugins/wordpress/source/igny8-wp-bridge/includes/template-functions.php @@ -208,6 +208,8 @@ function igny8_parse_keywords($keywords) { * @return array Array of badges with 'text' and 'type' keys */ function igny8_get_section_badges($heading, $post_id) { + static $used_keywords = []; + $badges = []; $heading_lower = strtolower($heading); @@ -217,39 +219,44 @@ function igny8_get_section_badges($heading, $post_id) { $primary_kw = get_post_meta($post_id, '_igny8_primary_keyword', true); $secondary_kws = get_post_meta($post_id, '_igny8_secondary_keywords', true); - // Priority 1: Primary keyword - if ($primary_kw && stripos($heading_lower, strtolower($primary_kw)) !== false) { + // Priority 1: Primary keyword (if not used) + if ($primary_kw && !in_array(strtolower($primary_kw), $used_keywords) && stripos($heading_lower, strtolower($primary_kw)) !== false) { $badges[] = ['text' => $primary_kw, 'type' => 'primary']; + $used_keywords[] = strtolower($primary_kw); + return $badges; // Return only 1 badge } - // Priority 2: Tags - if ($tags && !is_wp_error($tags) && count($badges) < 2) { + // Priority 2: Tags (if not used) + if ($tags && !is_wp_error($tags)) { foreach ($tags as $tag) { - if (stripos($heading_lower, strtolower($tag->name)) !== false) { + if (!in_array(strtolower($tag->name), $used_keywords) && stripos($heading_lower, strtolower($tag->name)) !== false) { $badges[] = ['text' => $tag->name, 'type' => 'tag']; - if (count($badges) >= 2) break; + $used_keywords[] = strtolower($tag->name); + return $badges; // Return only 1 badge } } } - // Priority 3: Categories - if ($categories && !is_wp_error($categories) && count($badges) < 2) { + // Priority 3: Categories (if not used) + if ($categories && !is_wp_error($categories)) { foreach ($categories as $cat) { - if (stripos($heading_lower, strtolower($cat->name)) !== false) { + if (!in_array(strtolower($cat->name), $used_keywords) && stripos($heading_lower, strtolower($cat->name)) !== false) { $badges[] = ['text' => $cat->name, 'type' => 'category']; - if (count($badges) >= 2) break; + $used_keywords[] = strtolower($cat->name); + return $badges; // Return only 1 badge } } } - // Priority 4: Secondary keywords - if ($secondary_kws && count($badges) < 2) { + // Priority 4: Secondary keywords (if not used) + if ($secondary_kws) { $kw_array = is_array($secondary_kws) ? $secondary_kws : explode(',', $secondary_kws); foreach ($kw_array as $kw) { $kw = trim($kw); - if (!empty($kw) && stripos($heading_lower, strtolower($kw)) !== false) { + if (!empty($kw) && !in_array(strtolower($kw), $used_keywords) && stripos($heading_lower, strtolower($kw)) !== false) { $badges[] = ['text' => $kw, 'type' => 'keyword']; - if (count($badges) >= 2) break; + $used_keywords[] = strtolower($kw); + return $badges; // Return only 1 badge } } } diff --git a/plugins/wordpress/source/igny8-wp-bridge/sync/igny8-to-wp.php b/plugins/wordpress/source/igny8-wp-bridge/sync/igny8-to-wp.php index 1a5a0ef5..c2ab1d2c 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/sync/igny8-to-wp.php +++ b/plugins/wordpress/source/igny8-wp-bridge/sync/igny8-to-wp.php @@ -854,6 +854,7 @@ function igny8_set_featured_image($post_id, $image_data) { */ function igny8_set_image_gallery($post_id, $gallery_images) { $attachment_ids = array(); + $imported_images = array(); // For _igny8_imported_images meta with full metadata // Limit to 5 images $gallery_images = array_slice($gallery_images, 0, 5); @@ -875,6 +876,19 @@ function igny8_set_image_gallery($post_id, $gallery_images) { if ($attachment_id) { $attachment_ids[] = $attachment_id; + + // Build complete image metadata for template + $position = is_array($image_data) ? ($image_data['position'] ?? 0) : 0; + $caption = is_array($image_data) ? ($image_data['caption'] ?? '') : ''; // Use caption instead of prompt + $is_featured = is_array($image_data) ? ($image_data['is_featured'] ?? false) : false; + + $imported_images[] = array( + 'position' => intval($position), + 'attachment_id' => $attachment_id, + 'url' => wp_get_attachment_url($attachment_id), + 'prompt' => $caption, // Store caption in prompt field for template compatibility + 'is_featured' => $is_featured + ); } } @@ -887,6 +901,14 @@ function igny8_set_image_gallery($post_id, $gallery_images) { update_post_meta($post_id, '_gallery_images', $attachment_ids); // Generic } + // Store complete image metadata for IGNY8 template + if (!empty($imported_images)) { + update_post_meta($post_id, '_igny8_imported_images', $imported_images); + + $log_prefix = "[" . get_option('igny8_site_id', 'N/A') . "-" . parse_url(site_url(), PHP_URL_HOST) . "]"; + Igny8_Logger::info("{$log_prefix} ✅ Saved _igny8_imported_images meta with " . count($imported_images) . " images (positions: " . implode(', ', array_column($imported_images, 'position')) . ")"); + } + return $attachment_ids; } diff --git a/plugins/wordpress/source/igny8-wp-bridge/templates/assets/css/igny8-content-template.css b/plugins/wordpress/source/igny8-wp-bridge/templates/assets/css/igny8-content-template.css index 5bdbb9b1..b7c8aaab 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/templates/assets/css/igny8-content-template.css +++ b/plugins/wordpress/source/igny8-wp-bridge/templates/assets/css/igny8-content-template.css @@ -144,6 +144,48 @@ font-size: 0.875rem; } +.igny8-meta-link { + color: inherit; + text-decoration: none; + transition: opacity 0.2s ease; +} + +.igny8-meta-link:hover { + opacity: 0.7; + text-decoration: underline; +} + +.igny8-category-badge, +.igny8-tag-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: var(--igny8-border-radius-xs); + font-size: 0.75rem; + font-weight: 600; + text-decoration: none; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.igny8-category-badge { + background: rgba(59, 130, 246, 0.15); + color: rgba(29, 78, 216, 1); +} + +.igny8-category-badge:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3); +} + +.igny8-tag-badge { + background: rgba(139, 92, 246, 0.15); + color: rgba(109, 40, 217, 1); +} + +.igny8-tag-badge:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(139, 92, 246, 0.3); +} + .igny8-meta-icon { font-size: 1rem; line-height: 1; @@ -235,29 +277,24 @@ /* === Featured Image === */ .igny8-featured-image-block { - background: var(--wp--preset--color--base, #ffffff); - border: 2px solid rgba(0, 0, 0, 0.12); - border-radius: var(--igny8-border-radius); - overflow: hidden; margin-bottom: var(--igny8-spacing); +} + +.igny8-image-card { + display: inline-block; + width: auto; + max-width: 100%; + border: 2px solid rgba(0, 0, 0, 0.12); + border-radius: var(--igny8-border-radius-md); + overflow: hidden; + background: var(--wp--preset--color--base, #ffffff); box-shadow: 0 4px 20px -4px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05); } -.igny8-featured-header { - padding: 2rem 2rem 1rem; -} - -.igny8-featured-label { - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.2em; - opacity: 0.6; -} - -.igny8-featured-image-wrapper { - position: relative; +.igny8-image-card img { + display: block; width: 100%; + height: auto; } .igny8-featured-image { @@ -268,28 +305,6 @@ margin: 0 auto; } -.igny8-image-prompt { - padding: 1.5rem 2rem; - border-top: 1px solid rgba(0, 0, 0, 0.08); - background: rgba(0, 0, 0, 0.02); -} - -.igny8-prompt-label { - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.15em; - opacity: 0.5; - margin: 0 0 0.75rem 0; -} - -.igny8-prompt-text { - font-size: 0.875rem; - line-height: 1.6; - margin: 0; - white-space: pre-wrap; -} - /* === Content Body === */ .igny8-content-body { display: flex; @@ -318,6 +333,34 @@ padding: 2rem; } +.igny8-intro-layout { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 2rem; + align-items: start; +} + +.igny8-intro-toc { + position: sticky; + top: 2rem; +} + +.igny8-intro-content { + font-size: 1.0625rem; + line-height: 1.85; + color: inherit; +} + +@media (max-width: 968px) { + .igny8-intro-layout { + grid-template-columns: 1fr; + } + + .igny8-intro-toc { + position: static; + } +} + .igny8-section-label { font-size: 0.7rem; font-weight: 700; @@ -369,18 +412,14 @@ } .igny8-section-content { - display: grid; - gap: 2.5rem; + display: block; + position: relative; } -.igny8-section-content.igny8-has-image { - grid-template-columns: 1fr; -} - -@media (min-width: 1024px) { - .igny8-section-content.igny8-has-image { - grid-template-columns: 3fr 2fr; - } +.igny8-section-content::after { + content: ""; + display: table; + clear: both; } /* === Prose Styles === */ @@ -420,6 +459,8 @@ .igny8-prose li { margin-bottom: 0.6rem; + clear: both; + max-width: 100%; } .igny8-prose a { @@ -491,12 +532,27 @@ margin: 3rem 0; } -/* === In-Article Images === */ -.igny8-image-figure { +/* =margin: 0; +} + +.igny8-image-figure.igny8-image-card { + display: inline-block; + width: auto; + max-width: 100%; border: 2px solid rgba(0, 0, 0, 0.12); border-radius: var(--igny8-border-radius-md); overflow: hidden; background: var(--wp--preset--color--base, #ffffff); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); +} + +.igny8-image-square-right, +.igny8-image-square-left { + border: 2px solid rgba(0, 0, 0, 0.12); + border-radius: var(--igny8-border-radius-md); + overflow: hidden; + background: var(--wp--preset--color--base, #ffffff) hidden; + background: var(--wp--preset--color--base, #ffffff); margin: 0; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); } @@ -521,17 +577,19 @@ /* Square image - Right aligned */ .igny8-image-square-right { - max-width: 50%; + width: 48%; + max-width: 48%; float: right; - margin-left: 2rem; + margin-left: 4%; margin-bottom: 2rem; } /* Square image - Left aligned */ .igny8-image-square-left { - max-width: 50%; + width: 48%; + max-width: 48%; float: left; - margin-right: 2rem; + margin-right: 4%; margin-bottom: 2rem; } @@ -546,24 +604,12 @@ /* Widget placeholder */ .igny8-widget-placeholder { - clear: both; - min-height: 200px; - padding: 1.5rem; - margin-top: 1rem; - background: rgba(0, 0, 0, 0.02); - border: 1px dashed rgba(0, 0, 0, 0.1); - border-radius: var(--igny8-border-radius-sm); - display: none; -} - -.igny8-widget-placeholder.igny8-widgets-enabled { - display: block; -} - -.igny8-image-caption { - padding: 1.25rem; + clear: botrem; border-top: 1px solid rgba(0, 0, 0, 0.08); -} + background: rgba(0, 0, 0, 0.02); + font-size: 0.875rem; + line-height: 1.6; + color: rgba(0, 0, 0, 0.7) .igny8-caption-label { font-size: 0.7rem; diff --git a/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-content-sections.php b/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-content-sections.php index 6dab9c5e..766e2da2 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-content-sections.php +++ b/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-content-sections.php @@ -33,11 +33,37 @@ $reuse_pattern = [1, 0, 3, 2]; // Featured, Square1, Landscape2, Square2
- + = $min_headings; + ?>
- -
- +
+ + + +
+ +
@@ -52,15 +78,13 @@ $reuse_pattern = [1, 0, 3, 2]; // Featured, Square1, Landscape2, Square2
+ if (!empty($badges) && isset($badges[0])): ?>
- $badge): ?> - - - - + + +

@@ -128,15 +152,14 @@ $reuse_pattern = [1, 0, 3, 2]; // Featured, Square1, Landscape2, Square2 } ?>
-
+
<?php echo esc_attr($section['heading']); ?>
-

Visual Direction

-

+
@@ -161,11 +184,16 @@ $reuse_pattern = [1, 0, 3, 2]; // Featured, Square1, Landscape2, Square2 loading="lazy">
-

Visual Direction

-

+
+
@@ -184,15 +212,14 @@ $reuse_pattern = [1, 0, 3, 2]; // Featured, Square1, Landscape2, Square2 } ?>
-
+
<?php echo esc_attr($section['heading']); ?>
-

Visual Direction

-

+
diff --git a/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-featured-image.php b/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-featured-image.php index 42d0bd44..3fd9b7c0 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-featured-image.php +++ b/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-featured-image.php @@ -20,22 +20,10 @@ if (!$image_url) { ?>
diff --git a/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-header.php b/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-header.php index 285fd42c..9a33be7d 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-header.php +++ b/plugins/wordpress/source/igny8-wp-bridge/templates/parts/igny8-header.php @@ -16,20 +16,9 @@ $status_class = igny8_get_status_class($status); ?>
- - - - +

- - -
@@ -64,13 +53,21 @@ $status_class = igny8_get_status_class($status);
- +
Topic: - + + + + +
@@ -82,8 +79,18 @@ $status_class = igny8_get_status_class($status); Categories:
- - name); ?> + Child) + $cat_hierarchy = []; + $current_cat = $cat; + while ($current_cat) { + array_unshift($cat_hierarchy, $current_cat); + $current_cat = ($current_cat->parent > 0) ? get_category($current_cat->parent) : null; + } + $hierarchy_text = implode(' > ', array_map(function($c) { return $c->name; }, $cat_hierarchy)); + ?> +
@@ -97,8 +104,12 @@ $status_class = igny8_get_status_class($status); Tags:
- - name); ?> + + name); ?>
diff --git a/plugins/wordpress/source/igny8-wp-bridge/templates/single-igny8-content.php b/plugins/wordpress/source/igny8-wp-bridge/templates/single-igny8-content.php index 9c86df36..33ff6b5b 100644 --- a/plugins/wordpress/source/igny8-wp-bridge/templates/single-igny8-content.php +++ b/plugins/wordpress/source/igny8-wp-bridge/templates/single-igny8-content.php @@ -80,8 +80,7 @@ if (defined('WP_DEBUG') && WP_DEBUG) { include plugin_dir_path(__FILE__) . 'parts/igny8-featured-image.php'; } - // Table of Contents - include plugin_dir_path(__FILE__) . 'parts/igny8-table-of-contents.php'; + // NOTE: Table of Contents is now rendered inside section 1 (see igny8-content-sections.php) // Content sections include plugin_dir_path(__FILE__) . 'parts/igny8-content-sections.php';