diff --git a/backend/igny8_core/ai/functions/generate_image_prompts.py b/backend/igny8_core/ai/functions/generate_image_prompts.py
index d9d12f2b..3474075f 100644
--- a/backend/igny8_core/ai/functions/generate_image_prompts.py
+++ b/backend/igny8_core/ai/functions/generate_image_prompts.py
@@ -197,12 +197,12 @@ class GenerateImagePromptsFunction(BaseAIFunction):
prompt_text = str(prompt_data)
caption_text = ''
- heading = h2_headings[idx] if idx < len(h2_headings) else f"Section {idx + 1}"
+ heading = h2_headings[idx] if idx < len(h2_headings) else f"Section {idx}"
Images.objects.update_or_create(
content=content,
image_type='in_article',
- position=idx + 1,
+ position=idx, # 0-based position matching section array indices
defaults={
'prompt': prompt_text,
'caption': caption_text,
diff --git a/frontend/src/templates/ContentViewTemplate.tsx b/frontend/src/templates/ContentViewTemplate.tsx
index 56e9ee9b..7d62d6ed 100644
--- a/frontend/src/templates/ContentViewTemplate.tsx
+++ b/frontend/src/templates/ContentViewTemplate.tsx
@@ -345,19 +345,76 @@ const IntroBlock = ({ html }: { html: string }) => (
);
+// Helper to split content at first H3 tag
+const splitAtFirstH3 = (html: string): { beforeH3: string; h3AndAfter: string } => {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+ const h3 = doc.querySelector('h3');
+
+ if (!h3) {
+ return { beforeH3: html, h3AndAfter: '' };
+ }
+
+ const beforeNodes: Node[] = [];
+ const afterNodes: Node[] = [];
+ let foundH3 = false;
+
+ Array.from(doc.body.childNodes).forEach((node) => {
+ if (node === h3) {
+ foundH3 = true;
+ afterNodes.push(node);
+ } else if (foundH3) {
+ afterNodes.push(node);
+ } else {
+ beforeNodes.push(node);
+ }
+ });
+
+ const serializeNodes = (nodes: Node[]): string =>
+ nodes
+ .map((node) => {
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ return (node as HTMLElement).outerHTML;
+ }
+ if (node.nodeType === Node.TEXT_NODE) {
+ return node.textContent ?? '';
+ }
+ return '';
+ })
+ .join('');
+
+ return {
+ beforeH3: serializeNodes(beforeNodes),
+ h3AndAfter: serializeNodes(afterNodes),
+ };
+};
+
+// Helper to check if section contains a table
+const hasTable = (html: string): boolean => {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, 'text/html');
+ return doc.querySelector('table') !== null;
+};
+
const ContentSectionBlock = ({
section,
image,
loading,
index,
+ imagePlacement = 'right',
+ firstImage = null,
}: {
section: ArticleSection;
image: ImageRecord | null;
loading: boolean;
index: number;
+ imagePlacement?: 'left' | 'center' | 'right';
+ firstImage?: ImageRecord | null;
}) => {
const hasImage = Boolean(image);
const headingLabel = section.heading || `Section ${index + 1}`;
+ const sectionHasTable = hasTable(section.bodyHtml);
+ const { beforeH3, h3AndAfter } = splitAtFirstH3(section.bodyHtml);
return (