Expand [caption] shortcodes instead of emitting them as text
WordPress expands shortcodes at render time via do_shortcode(), so the raw post_content the export reads still contains them literally. All ten [caption] instances were being written straight into the Markdown and rendered as visible markup -- the Hosting page led with [caption id="attachment_205" align="aligncenter" width="300"]. They now become the same div.wp-caption / p.wp-caption-text structure WordPress produces, which the ported theme CSS already styles. The shortcode is swapped for a placeholder before the HTML->Markdown pass and rebuilt after, so the image inside stays Markdown and still goes through Astro's image pipeline rather than being frozen as raw HTML pointing into src/assets, which would not resolve at runtime. [caption] is the only shortcode in use -- no gallery/embed/video/audio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+40
-2
@@ -100,6 +100,35 @@ function rewriteInternalLinks(html, linkMap, unresolved) {
|
||||
);
|
||||
}
|
||||
|
||||
// WordPress expands [caption] at render time via do_shortcode(); the raw post_content keeps it
|
||||
// literal, so it has to be expanded here or it shows up as visible markup on the page.
|
||||
// The shortcode is swapped for a placeholder before the HTML->Markdown conversion and rebuilt
|
||||
// afterwards, so the <img> inside still goes through Turndown (and therefore still gets picked
|
||||
// up by Astro's image pipeline) rather than being frozen as raw HTML pointing into src/assets.
|
||||
const CAPTION_PLACEHOLDER = (i) => `CAPTIONPLACEHOLDER${i}END`;
|
||||
|
||||
function extractCaptions(html, captions) {
|
||||
return html.replace(/\[caption([^\]]*)\]([\s\S]*?)\[\/caption\]/gi, (_match, attrs, inner) => {
|
||||
const lastTag = inner.lastIndexOf('>');
|
||||
captions.push({
|
||||
align: (attrs.match(/align=["']?(align\w+)["']?/i) || [, 'aligncenter'])[1],
|
||||
width: (attrs.match(/width=["']?(\d+)["']?/i) || [, ''])[1],
|
||||
mediaHtml: inner.slice(0, lastTag + 1),
|
||||
text: inner.slice(lastTag + 1).trim(),
|
||||
});
|
||||
return CAPTION_PLACEHOLDER(captions.length - 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Blank lines around the image are load-bearing: they close the surrounding raw HTML block so
|
||||
// the image is parsed as Markdown rather than swallowed as literal HTML.
|
||||
function buildCaption({ align, width, mediaHtml, text }) {
|
||||
const media = turndown.turndown(mediaHtml).trim();
|
||||
const style = width ? ` style="width: ${Number(width) + 10}px"` : '';
|
||||
const caption = text ? `\n<p class="wp-caption-text">${text}</p>` : '';
|
||||
return `<div class="wp-caption ${align}"${style}>\n\n${media}\n${caption}\n</div>`;
|
||||
}
|
||||
|
||||
async function buildLinkMap(conn) {
|
||||
const [rows] = await conn.execute(
|
||||
`SELECT ID, post_type, post_name FROM ${TABLE_PREFIX}posts
|
||||
@@ -134,11 +163,20 @@ async function exportPostType(conn, postType, destDir, { attachmentPaths, thumbn
|
||||
}
|
||||
html = rewriteImageUrls(html, destDir);
|
||||
html = rewriteInternalLinks(html, linkMap, unresolved);
|
||||
const markdown = turndown.turndown(html);
|
||||
|
||||
const captions = [];
|
||||
html = extractCaptions(html, captions);
|
||||
const markdown = turndown
|
||||
.turndown(html)
|
||||
.replace(/CAPTIONPLACEHOLDER(\d+)END/g, (_m, i) => buildCaption(captions[Number(i)]));
|
||||
|
||||
let excerpt = fixLiteralNewlines(row.post_excerpt || '');
|
||||
if (!excerpt) {
|
||||
const plain = html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
const plain = html
|
||||
.replace(/CAPTIONPLACEHOLDER\d+END/g, ' ')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
// 60 words: what the theme's front-page loop sets ($sbExcerptLength = 60).
|
||||
// The slideshow truncates this further to 30 at render, as the theme does.
|
||||
excerpt = plain.split(' ').slice(0, 60).join(' ');
|
||||
|
||||
Reference in New Issue
Block a user