-
-
Notifications
You must be signed in to change notification settings - Fork 89
Description
The header card HTML parser in header-parser.js always defaults to split layout when an image is present, ignoring the correct HTML structure and classes that indicate full/wide layout.
Related Forum Discussion: https://forum.ghost.org/t/header-card-images-displaying-side-by-side-instead-of-as-background/60982/8
Steps to Reproduce
- Create a header card in Ghost editor with full/wide layout (background image mode)
- Export the HTML from that card
- Send that same HTML back to Ghost via Admin API using HTML source
- Open the imported card in the editor
Actual Result: Card shows as split layout in the editor
Expected Result: Card should maintain full/wide layout with image as background
The Bug
In packages/kg-default-nodes/lib/nodes/header/parsers/header-parser.js (referenced in forum post), the parser has this logic:
const layout = backgroundImageSrc ? 'split' : '';This line (line 11 in the referenced code) defaults to split whenever an image exists, ignoring:
- The presence/absence of
kg-layout-splitclass - The presence of
kg-content-wideclass (which indicates background image mode) - The DOM structure (picture as direct child = full, picture inside content = split)
Correct HTML Structure
Full/Wide Layout (Background Image)
For full/wide layout (background image), the HTML structure should be:
<div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide" data-background-color="#000000">
<picture><img class="kg-header-card-image" src="https://example.com/image.jpg" /></picture>
<div class="kg-header-card-content">
<div class="kg-header-card-text kg-align-center">
<h2 class="kg-header-card-heading" style="color: #FFFFFF;">Title</h2>
<p class="kg-header-card-subheading" style="color: #FFFFFF;">Subtitle</p>
<a class="kg-header-card-button" href="#">Button</a>
</div>
</div>
</div>Key indicators of full layout:
- ✅
kg-content-wideclass present - ✅ NO
kg-layout-splitclass - ✅ Picture element as direct child of card (before content div)
Split Layout
For split layout, the structure is:
<div class="kg-card kg-header-card kg-v2 kg-layout-split kg-width-full">
<div class="kg-header-card-content">
<picture><img class="kg-header-card-image" src="https://example.com/image.jpg" /></picture>
<div class="kg-header-card-text kg-align-center">
<!-- content -->
</div>
</div>
</div>Key indicators of split layout:
- ✅
kg-layout-splitclass present - ❌ NO
kg-content-wideclass - ✅ Picture element inside content div
Proposed Fix
The parser should check for the layout classes or DOM structure, not just image presence:
// Check for explicit layout class first
const hasSplitClass = div.classList.contains('kg-layout-split');
const hasContentWideClass = div.classList.contains('kg-content-wide');
// Check DOM structure: picture as direct child = full, picture inside content = split
const picture = div.querySelector('picture');
const content = div.querySelector('.kg-header-card-content');
const isPictureDirectChild = picture && picture.parentElement === div;
const isPictureInContent = picture && content && content.contains(picture);
// Determine layout
let layout = '';
if (hasSplitClass || isPictureInContent) {
layout = 'split';
} else if (hasContentWideClass || (backgroundImageSrc && isPictureDirectChild)) {
layout = ''; // full/wide layout (empty string)
}Test Case
HTML that should result in full layout but currently becomes split:
<div class="kg-card kg-header-card kg-v2 kg-width-full kg-content-wide" data-background-color="#000000">
<picture><img class="kg-header-card-image" src="https://example.com/image.jpg" /></picture>
<div class="kg-header-card-content">
<div class="kg-header-card-text kg-align-center">
<h2 class="kg-header-card-heading" style="color: #FFFFFF;">Title</h2>
<p class="kg-header-card-subheading" style="color: #FFFFFF;">Subtitle</p>
</div>
</div>
</div>What we're sending to API:
- Classes:
kg-v2 kg-width-full kg-content-wide(nokg-layout-split) - Structure: Picture as direct child of card
- Logs confirm:
split=False, full=True, wide_content=True, pic=direct child
What happens:
- Ghost converts HTML to Mobiledoc
- Parser sees image exists → defaults to
layout = 'split' - Editor shows card as split layout
Impact
This affects anyone programmatically creating header cards via the Admin API using HTML. The HTML-to-Mobiledoc conversion incorrectly infers split layout, requiring:
- Manual correction in the editor for each card, OR
- JavaScript workarounds to fix layout after render, OR
- Switching to Lexical format (major rewrite)
Context
- Ghost Version: 6.9+
- API Version: Admin API v5.0
- Use Case: Programmatically generating monthly newsletter content via Python script
- Method: Sending HTML directly to Admin API
postsendpoint with?source=html
The HTML structure is validated as correct before sending (matches exported HTML from working cards), but Ghost's parser misinterprets it during HTML-to-Mobiledoc conversion.
Verification
I've verified the HTML being sent:
- Logs show correct classes:
['kg-card', 'kg-header-card', 'kg-v2', 'kg-width-full', 'kg-content-wide'] - Structure verified: Picture is direct child, not inside content div
- No
kg-layout-splitclass present - Structure matches exported HTML from working cards
The bug is confirmed by Cathy_Sarisky in the forum, who tested and confirmed the same behavior.