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;
+ ?>
- Opening Narrative
-
-
+
+
+
+
+
+
+
@@ -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
}
?>
-
+
- 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
}
?>
-
+
- 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);
?>
-
+
@@ -82,8 +79,18 @@ $status_class = igny8_get_status_class($status);
Categories:
@@ -97,8 +104,12 @@ $status_class = igny8_get_status_class($status);
Tags:
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';