@marvalt/wparser 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1595,6 +1595,57 @@ function getImageAttributes(block) {
1595
1595
  height: attrs['height'] ? Number(attrs['height']) : undefined,
1596
1596
  };
1597
1597
  }
1598
+ /**
1599
+ * Validate if a Cloudflare URL is complete (has image ID)
1600
+ * A complete Cloudflare URL should have format: https://imagedelivery.net/{account}/{image-id}/
1601
+ */
1602
+ function isValidCloudflareUrl(url) {
1603
+ if (!url)
1604
+ return false;
1605
+ // Check if it's a Cloudflare URL
1606
+ if (!url.includes('imagedelivery.net'))
1607
+ return false;
1608
+ // A complete URL should have at least 3 path segments: /account/image-id/
1609
+ // Incomplete URLs might end with just /account/ or /account
1610
+ const urlObj = new URL(url);
1611
+ const pathSegments = urlObj.pathname.split('/').filter(Boolean);
1612
+ // Should have at least account hash and image ID (2 segments minimum)
1613
+ // But we also accept URLs that end with / (which means they might be complete)
1614
+ // The issue is URLs like: https://imagedelivery.net/ZFArYcvsK9lQ3btUK-x2rA/
1615
+ // This is incomplete - it's missing the image ID
1616
+ return pathSegments.length >= 2;
1617
+ }
1618
+ /**
1619
+ * Extract image URL from block with priority:
1620
+ * 1. Valid cloudflareUrl from attributes
1621
+ * 2. Extract from innerHTML (which should be converted by plugin)
1622
+ * 3. Regular URL attributes
1623
+ *
1624
+ * This handles cases where cloudflareUrl might be incomplete
1625
+ */
1626
+ function extractImageUrlWithFallback(block) {
1627
+ const attrs = block.attributes || {};
1628
+ // Check for cloudflareUrl first (from WordPress plugin)
1629
+ const cloudflareUrl = attrs['cloudflareUrl'];
1630
+ if (cloudflareUrl && isValidCloudflareUrl(cloudflareUrl)) {
1631
+ return cloudflareUrl;
1632
+ }
1633
+ // Try to extract from innerHTML (should be converted by plugin)
1634
+ if (block.innerHTML) {
1635
+ // Extract img src from innerHTML
1636
+ const imgMatch = block.innerHTML.match(/<img[^>]+src=["']([^"']+)["']/i);
1637
+ if (imgMatch && imgMatch[1]) {
1638
+ return imgMatch[1];
1639
+ }
1640
+ // Try background-image in style attribute
1641
+ const bgMatch = block.innerHTML.match(/background-image:\s*url\(["']?([^"')]+)["']?\)/i);
1642
+ if (bgMatch && bgMatch[1]) {
1643
+ return bgMatch[1];
1644
+ }
1645
+ }
1646
+ // Fall back to regular URL attributes
1647
+ return getImageUrl(block);
1648
+ }
1598
1649
 
1599
1650
  /**
1600
1651
  * Style mapping utilities
@@ -1837,28 +1888,28 @@ const ButtonBlock = ({ block }) => {
1837
1888
  };
1838
1889
  const Cover = ({ block, children }) => {
1839
1890
  const attrs = block.attributes || {};
1840
- const { url, id, backgroundImage, overlayColor, dimRatio = 0, align = 'full', minHeight, minHeightUnit = 'vh', hasParallax, } = attrs;
1891
+ const { url, id, backgroundImage, cloudflareUrl, overlayColor, dimRatio = 0, align = 'full', minHeight, minHeightUnit = 'vh', hasParallax, } = attrs;
1841
1892
  // Get background image URL from various possible sources
1842
- let bgImageUrl = url || backgroundImage || (typeof backgroundImage === 'object' && backgroundImage?.url);
1843
- // If not found in attributes, try to extract from innerHTML
1844
- if (!bgImageUrl && block.innerHTML) {
1845
- // Try to extract img src from innerHTML
1846
- const imgMatch = block.innerHTML.match(/<img[^>]+src=["']([^"']+)["']/i);
1847
- if (imgMatch && imgMatch[1]) {
1848
- bgImageUrl = imgMatch[1];
1849
- }
1850
- // Try background-image in style attribute
1851
- if (!bgImageUrl) {
1852
- const bgMatch = block.innerHTML.match(/background-image:\s*url\(["']?([^"')]+)["']?\)/i);
1853
- if (bgMatch && bgMatch[1]) {
1854
- bgImageUrl = bgMatch[1];
1855
- }
1856
- }
1893
+ // Use the improved extraction function that handles incomplete cloudflareUrl
1894
+ let bgImageUrl = null;
1895
+ // First, try cloudflareUrl if it's valid
1896
+ if (cloudflareUrl && isValidCloudflareUrl(cloudflareUrl)) {
1897
+ bgImageUrl = cloudflareUrl;
1898
+ }
1899
+ // If not valid or not found, try regular attributes
1900
+ if (!bgImageUrl) {
1901
+ bgImageUrl = url || backgroundImage || (typeof backgroundImage === 'object' && backgroundImage?.url) || null;
1857
1902
  }
1858
- // Convert to Cloudflare URL if it's a Cloudflare image, otherwise use as-is
1859
- if (bgImageUrl && isCloudflareImageUrl(bgImageUrl)) {
1860
- // Use full width for cover images
1861
- bgImageUrl = getCloudflareVariantUrl(bgImageUrl, { width: 1920 });
1903
+ // If still not found, use the fallback extraction (from innerHTML)
1904
+ if (!bgImageUrl) {
1905
+ bgImageUrl = extractImageUrlWithFallback(block);
1906
+ }
1907
+ // Convert to Cloudflare URL variant if it's a Cloudflare image
1908
+ if (bgImageUrl) {
1909
+ if (isCloudflareImageUrl(bgImageUrl)) {
1910
+ // Use full width for cover images
1911
+ bgImageUrl = getCloudflareVariantUrl(bgImageUrl, { width: 1920 });
1912
+ }
1862
1913
  }
1863
1914
  // Build alignment classes
1864
1915
  const alignClass = getAlignmentClasses(align);
@@ -1885,20 +1936,42 @@ const Cover = ({ block, children }) => {
1885
1936
  };
1886
1937
  const MediaText = ({ block, children, context }) => {
1887
1938
  const attrs = block.attributes || {};
1888
- const { mediaPosition = 'left', verticalAlignment = 'center', imageFill = false, align = 'wide', } = attrs;
1939
+ const { mediaPosition = 'left', verticalAlignment = 'center', imageFill = false, align, } = attrs;
1889
1940
  // Access innerBlocks to identify media vs content
1890
1941
  const innerBlocks = block.innerBlocks || [];
1891
1942
  // Find media block (image or video)
1892
- const mediaBlockIndex = innerBlocks.findIndex((b) => b.name === 'core/image' || b.name === 'core/video');
1943
+ let mediaBlockIndex = innerBlocks.findIndex((b) => b.name === 'core/image' || b.name === 'core/video');
1893
1944
  // Render children - media-text typically has media as first child, then content
1894
1945
  const childrenArray = React.Children.toArray(children);
1895
- const mediaElement = mediaBlockIndex >= 0 && childrenArray[mediaBlockIndex]
1946
+ let mediaElement = mediaBlockIndex >= 0 && childrenArray[mediaBlockIndex]
1896
1947
  ? childrenArray[mediaBlockIndex]
1897
1948
  : null;
1949
+ // If no media element from innerBlocks, try to extract image URL
1950
+ if (!mediaElement) {
1951
+ const imageUrl = extractImageUrlWithFallback(block);
1952
+ if (imageUrl) {
1953
+ // Convert to Cloudflare variant if it's a Cloudflare URL
1954
+ const finalImageUrl = isCloudflareImageUrl(imageUrl)
1955
+ ? getCloudflareVariantUrl(imageUrl, { width: 1024 })
1956
+ : imageUrl;
1957
+ mediaElement = (jsxRuntimeExports.jsx("img", { src: finalImageUrl, alt: "", className: "w-full h-auto rounded-lg object-cover", loading: "lazy" }));
1958
+ }
1959
+ }
1898
1960
  // Content is all other children
1899
1961
  const contentElements = childrenArray.filter((_, index) => index !== mediaBlockIndex);
1900
- // Build alignment classes
1901
- const alignClass = getAlignmentClasses(align) || 'max-w-7xl mx-auto';
1962
+ // Build alignment classes - ensure proper container width
1963
+ // For 'wide', use max-w-7xl; for 'full', use w-full; default to contained
1964
+ let alignClass;
1965
+ if (align === 'full') {
1966
+ alignClass = 'w-full';
1967
+ }
1968
+ else if (align === 'wide') {
1969
+ alignClass = 'max-w-7xl mx-auto';
1970
+ }
1971
+ else {
1972
+ // Default to contained width (not full width)
1973
+ alignClass = 'container mx-auto';
1974
+ }
1902
1975
  // Vertical alignment classes
1903
1976
  const verticalAlignClass = verticalAlignment === 'top' ? 'items-start' :
1904
1977
  verticalAlignment === 'bottom' ? 'items-end' :
@@ -1907,7 +1980,9 @@ const MediaText = ({ block, children, context }) => {
1907
1980
  const stackClass = 'flex-col md:flex-row';
1908
1981
  // Media position determines order
1909
1982
  const isMediaRight = mediaPosition === 'right';
1910
- return (jsxRuntimeExports.jsx("div", { className: buildClassName(alignClass, 'px-4'), children: jsxRuntimeExports.jsxs("div", { className: buildClassName('flex', stackClass, verticalAlignClass, 'gap-6 lg:gap-12'), children: [jsxRuntimeExports.jsx("div", { className: buildClassName(isMediaRight ? 'order-2' : 'order-1', imageFill ? 'w-full md:w-1/2' : 'flex-shrink-0'), children: mediaElement || jsxRuntimeExports.jsx("div", { className: "bg-gray-200 h-64 rounded-lg" }) }), jsxRuntimeExports.jsx("div", { className: buildClassName(isMediaRight ? 'order-1' : 'order-2', imageFill ? 'w-full md:w-1/2' : 'flex-1', 'space-y-4'), children: contentElements.length > 0 ? contentElements : children })] }) }));
1983
+ // Add section spacing for consistent vertical rhythm
1984
+ const spacingClass = getSectionSpacingClasses();
1985
+ return (jsxRuntimeExports.jsx("div", { className: buildClassName(alignClass, spacingClass, 'px-4'), children: jsxRuntimeExports.jsxs("div", { className: buildClassName('flex', stackClass, verticalAlignClass, 'gap-6 lg:gap-12'), children: [jsxRuntimeExports.jsx("div", { className: buildClassName(isMediaRight ? 'order-2' : 'order-1', imageFill ? 'w-full md:w-1/2' : 'flex-shrink-0 md:w-1/2'), children: mediaElement || jsxRuntimeExports.jsx("div", { className: "bg-gray-200 h-64 rounded-lg" }) }), jsxRuntimeExports.jsx("div", { className: buildClassName(isMediaRight ? 'order-1' : 'order-2', 'md:w-1/2', getContentSpacingClasses()), children: contentElements.length > 0 ? contentElements : children })] }) }));
1911
1986
  };
1912
1987
  const Fallback = ({ block, children }) => {
1913
1988
  // Minimal fallback; do not render innerHTML directly in v1 for safety
@@ -2179,7 +2254,7 @@ class WPErrorBoundary extends React.Component {
2179
2254
 
2180
2255
  /**
2181
2256
  * Extract background image URL from a block
2182
- * Checks various possible sources: url, backgroundImage, innerHTML, featured image
2257
+ * Checks various possible sources: cloudflareUrl, url, backgroundImage, innerHTML, featured image
2183
2258
  */
2184
2259
  function extractBackgroundImage(block, page) {
2185
2260
  const attrs = block.attributes || {};
@@ -2187,34 +2262,17 @@ function extractBackgroundImage(block, page) {
2187
2262
  if (attrs['useFeaturedImage'] === true && page?._embedded?.['wp:featuredmedia']?.[0]?.source_url) {
2188
2263
  return page._embedded['wp:featuredmedia'][0].source_url;
2189
2264
  }
2190
- // Try various attribute keys
2191
- let url = attrs['url'] ||
2192
- attrs['backgroundImage'] ||
2193
- (typeof attrs['backgroundImage'] === 'object' && attrs['backgroundImage']?.url);
2194
- if (typeof url === 'string' && url.trim()) {
2195
- return url.trim();
2196
- }
2197
- // Try to extract from innerHTML if not found in attributes
2198
- if (block.innerHTML) {
2199
- // Try img src from innerHTML
2200
- const imgMatch = block.innerHTML.match(/<img[^>]+src=["']([^"']+)["']/i);
2201
- if (imgMatch && imgMatch[1]) {
2202
- return imgMatch[1];
2203
- }
2204
- // Try background-image in style attribute
2205
- const bgMatch = block.innerHTML.match(/background-image:\s*url\(["']?([^"')]+)["']?\)/i);
2206
- if (bgMatch && bgMatch[1]) {
2207
- return bgMatch[1];
2208
- }
2209
- }
2210
- return null;
2265
+ // Use the improved extraction function that handles incomplete cloudflareUrl
2266
+ // This will check cloudflareUrl first, then innerHTML, then regular attributes
2267
+ return extractImageUrlWithFallback(block);
2211
2268
  }
2212
2269
  /**
2213
2270
  * Extract image URL from a block
2214
2271
  * Returns Cloudflare URL if available, otherwise WordPress URL
2215
2272
  */
2216
2273
  function extractImageUrl(block) {
2217
- return getImageUrl(block);
2274
+ // Use the improved extraction function that handles incomplete cloudflareUrl
2275
+ return extractImageUrlWithFallback(block);
2218
2276
  }
2219
2277
  /**
2220
2278
  * Extract image attributes (url, alt, width, height)
@@ -2617,6 +2675,7 @@ exports.extractFontSize = extractFontSize;
2617
2675
  exports.extractHeadingLevel = extractHeadingLevel;
2618
2676
  exports.extractImageAttributes = extractImageAttributes;
2619
2677
  exports.extractImageUrl = extractImageUrl;
2678
+ exports.extractImageUrlWithFallback = extractImageUrlWithFallback;
2620
2679
  exports.extractMediaPosition = extractMediaPosition;
2621
2680
  exports.extractMinHeight = extractMinHeight;
2622
2681
  exports.extractOverlayColor = extractOverlayColor;
@@ -2641,6 +2700,7 @@ exports.getImageUrl = getImageUrl;
2641
2700
  exports.getSectionSpacingClasses = getSectionSpacingClasses;
2642
2701
  exports.getTextAlignClasses = getTextAlignClasses;
2643
2702
  exports.isCloudflareImageUrl = isCloudflareImageUrl;
2703
+ exports.isValidCloudflareUrl = isValidCloudflareUrl;
2644
2704
  exports.matchesPattern = matchesPattern;
2645
2705
  exports.parseContentPosition = parseContentPosition;
2646
2706
  exports.parseGutenbergBlocks = parseGutenbergBlocks;