@marvalt/wparser 0.1.5 → 0.1.7

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.esm.js CHANGED
@@ -1515,7 +1515,7 @@ function extractTextFromHTML(html) {
1515
1515
  if (!html)
1516
1516
  return '';
1517
1517
  // Remove HTML tags and decode entities
1518
- const text = html
1518
+ let text = html
1519
1519
  .replace(/<[^>]*>/g, '') // Remove HTML tags
1520
1520
  .replace(/&nbsp;/g, ' ') // Replace &nbsp; with space
1521
1521
  .replace(/&#8217;/g, "'") // Replace apostrophe entity
@@ -1526,7 +1526,11 @@ function extractTextFromHTML(html) {
1526
1526
  .replace(/&lt;/g, '<') // Replace &lt;
1527
1527
  .replace(/&gt;/g, '>') // Replace &gt;
1528
1528
  .replace(/&quot;/g, '"') // Replace &quot;
1529
+ .replace(/&#8211;/g, '–') // Replace en dash
1530
+ .replace(/&#8212;/g, '—') // Replace em dash
1529
1531
  .trim();
1532
+ // Clean up extra whitespace
1533
+ text = text.replace(/\s+/g, ' ');
1530
1534
  return text;
1531
1535
  }
1532
1536
  /**
@@ -1548,11 +1552,12 @@ function getBlockTextContent(block) {
1548
1552
  /**
1549
1553
  * Extract image URL from block attributes
1550
1554
  * Checks for Cloudflare URLs first, then falls back to regular URLs
1555
+ * Also extracts from innerHTML if needed
1551
1556
  */
1552
1557
  function getImageUrl(block) {
1553
1558
  const attrs = block.attributes || {};
1554
1559
  // Check various possible URL attributes
1555
- const url = attrs['url'] ||
1560
+ let url = attrs['url'] ||
1556
1561
  attrs['src'] ||
1557
1562
  attrs['imageUrl'] ||
1558
1563
  attrs['mediaUrl'] ||
@@ -1562,10 +1567,16 @@ function getImageUrl(block) {
1562
1567
  }
1563
1568
  // Try to extract from innerHTML if it's an img tag
1564
1569
  if (block.innerHTML) {
1565
- const imgMatch = block.innerHTML.match(/src=["']([^"']+)["']/);
1570
+ // Try img src first
1571
+ const imgMatch = block.innerHTML.match(/<img[^>]+src=["']([^"']+)["']/i);
1566
1572
  if (imgMatch && imgMatch[1]) {
1567
1573
  return imgMatch[1];
1568
1574
  }
1575
+ // Try background-image in style attribute
1576
+ const bgMatch = block.innerHTML.match(/background-image:\s*url\(["']?([^"')]+)["']?\)/i);
1577
+ if (bgMatch && bgMatch[1]) {
1578
+ return bgMatch[1];
1579
+ }
1569
1580
  }
1570
1581
  return null;
1571
1582
  }
@@ -1798,8 +1809,22 @@ const Cover = ({ block, children }) => {
1798
1809
  const { url, id, backgroundImage, overlayColor, dimRatio = 0, align = 'full', minHeight, minHeightUnit = 'vh', hasParallax, } = attrs;
1799
1810
  // Get background image URL from various possible sources
1800
1811
  let bgImageUrl = url || backgroundImage || (typeof backgroundImage === 'object' && backgroundImage?.url);
1801
- // If we have an image ID, try to get Cloudflare URL from media data
1802
- // For now, use the URL as-is (should already be Cloudflare if processed)
1812
+ // If not found in attributes, try to extract from innerHTML
1813
+ if (!bgImageUrl && block.innerHTML) {
1814
+ // Try to extract img src from innerHTML
1815
+ const imgMatch = block.innerHTML.match(/<img[^>]+src=["']([^"']+)["']/i);
1816
+ if (imgMatch && imgMatch[1]) {
1817
+ bgImageUrl = imgMatch[1];
1818
+ }
1819
+ // Try background-image in style attribute
1820
+ if (!bgImageUrl) {
1821
+ const bgMatch = block.innerHTML.match(/background-image:\s*url\(["']?([^"')]+)["']?\)/i);
1822
+ if (bgMatch && bgMatch[1]) {
1823
+ bgImageUrl = bgMatch[1];
1824
+ }
1825
+ }
1826
+ }
1827
+ // Convert to Cloudflare URL if it's a Cloudflare image, otherwise use as-is
1803
1828
  if (bgImageUrl && isCloudflareImageUrl(bgImageUrl)) {
1804
1829
  // Use full width for cover images
1805
1830
  bgImageUrl = getCloudflareVariantUrl(bgImageUrl, { width: 1920 });
@@ -1822,10 +1847,10 @@ const Cover = ({ block, children }) => {
1822
1847
  }
1823
1848
  // Calculate overlay opacity
1824
1849
  const overlayOpacity = typeof dimRatio === 'number' ? dimRatio / 100 : 0;
1825
- return (jsxRuntimeExports.jsxs("div", { className: buildClassName('relative', alignClass), style: style, children: [overlayOpacity > 0 && (jsxRuntimeExports.jsx("span", { className: "absolute inset-0", style: {
1826
- backgroundColor: overlayColor || '#000000',
1850
+ return (jsxRuntimeExports.jsxs("div", { className: buildClassName('relative w-full', alignClass), style: style, children: [overlayOpacity > 0 && (jsxRuntimeExports.jsx("span", { className: "absolute inset-0 z-0", style: {
1851
+ backgroundColor: overlayColor === 'contrast' ? '#000000' : (overlayColor || '#000000'),
1827
1852
  opacity: overlayOpacity,
1828
- }, "aria-hidden": "true" })), jsxRuntimeExports.jsx("div", { className: "relative z-10 container", children: children })] }));
1853
+ }, "aria-hidden": "true" })), jsxRuntimeExports.jsx("div", { className: buildClassName('relative z-10', align === 'full' ? 'w-full' : 'container mx-auto px-4'), children: children })] }));
1829
1854
  };
1830
1855
  const MediaText = ({ block, children, context }) => {
1831
1856
  const attrs = block.attributes || {};
@@ -1906,6 +1931,138 @@ function getString(block) {
1906
1931
  return getBlockTextContent(block);
1907
1932
  }
1908
1933
 
1934
+ /**
1935
+ * Check if a block matches a pattern
1936
+ */
1937
+ function matchesPattern(block, pattern) {
1938
+ // Check block name
1939
+ if (block.name !== pattern.name) {
1940
+ return false;
1941
+ }
1942
+ // Check attributes if specified
1943
+ if (pattern.attributes) {
1944
+ const blockAttrs = block.attributes || {};
1945
+ for (const [key, value] of Object.entries(pattern.attributes)) {
1946
+ if (blockAttrs[key] !== value) {
1947
+ return false;
1948
+ }
1949
+ }
1950
+ }
1951
+ // Check innerBlocks patterns if specified
1952
+ if (pattern.innerBlocks && pattern.innerBlocks.length > 0) {
1953
+ const blockInnerBlocks = block.innerBlocks || [];
1954
+ // If pattern specifies innerBlocks, check if block has matching innerBlocks
1955
+ for (const innerPattern of pattern.innerBlocks) {
1956
+ // Find at least one matching innerBlock
1957
+ const hasMatch = blockInnerBlocks.some(innerBlock => matchesPattern(innerBlock, innerPattern));
1958
+ if (!hasMatch) {
1959
+ return false;
1960
+ }
1961
+ }
1962
+ }
1963
+ return true;
1964
+ }
1965
+ /**
1966
+ * Find the best matching component mapping for a block
1967
+ * Returns the mapping with highest priority that matches, or null
1968
+ */
1969
+ function findMatchingMapping(block, mappings) {
1970
+ // Sort by priority (higher first), then by order in array
1971
+ const sortedMappings = [...mappings].sort((a, b) => {
1972
+ const priorityA = a.priority ?? 0;
1973
+ const priorityB = b.priority ?? 0;
1974
+ if (priorityA !== priorityB) {
1975
+ return priorityB - priorityA; // Higher priority first
1976
+ }
1977
+ return 0; // Keep original order for same priority
1978
+ });
1979
+ // Find first matching mapping
1980
+ for (const mapping of sortedMappings) {
1981
+ if (matchesPattern(block, mapping.pattern)) {
1982
+ return mapping;
1983
+ }
1984
+ }
1985
+ return null;
1986
+ }
1987
+
1988
+ /**
1989
+ * Create an enhanced registry that supports pattern-based component mapping
1990
+ *
1991
+ * This combines the default registry (for fallback) with app-specific component mappings.
1992
+ * When a block matches a pattern, it uses the mapped component. Otherwise, it falls back
1993
+ * to the default renderer.
1994
+ *
1995
+ * @param mappings - Array of component mappings with patterns
1996
+ * @param baseRegistry - Optional base registry (defaults to createDefaultRegistry())
1997
+ * @returns Enhanced registry with pattern matching capabilities
1998
+ *
1999
+ * @example
2000
+ * ```ts
2001
+ * const mappings: ComponentMapping[] = [
2002
+ * {
2003
+ * pattern: { name: 'core/cover' },
2004
+ * component: HomeHeroSection,
2005
+ * extractProps: (block) => ({
2006
+ * backgroundImage: extractBackgroundImage(block),
2007
+ * title: extractTitle(block),
2008
+ * }),
2009
+ * wrapper: SectionWrapper,
2010
+ * },
2011
+ * ];
2012
+ *
2013
+ * const registry = createEnhancedRegistry(mappings);
2014
+ * ```
2015
+ */
2016
+ function createEnhancedRegistry(mappings = [], baseRegistry) {
2017
+ const base = baseRegistry || createDefaultRegistry();
2018
+ // Create enhanced renderers that check patterns first
2019
+ const enhancedRenderers = {
2020
+ ...base.renderers,
2021
+ };
2022
+ // Override renderers for blocks that have mappings
2023
+ // We need to check patterns at render time, so we create a wrapper renderer
2024
+ const createPatternRenderer = (blockName) => {
2025
+ return (props) => {
2026
+ const { block, context } = props;
2027
+ // Find matching mapping
2028
+ const mapping = findMatchingMapping(block, mappings);
2029
+ if (mapping) {
2030
+ // Extract props from block
2031
+ const componentProps = mapping.extractProps(block, context);
2032
+ // Render component
2033
+ const Component = mapping.component;
2034
+ const content = jsxRuntimeExports.jsx(Component, { ...componentProps });
2035
+ // Wrap with wrapper if provided
2036
+ if (mapping.wrapper) {
2037
+ const Wrapper = mapping.wrapper;
2038
+ return jsxRuntimeExports.jsx(Wrapper, { block: block, children: content });
2039
+ }
2040
+ return content;
2041
+ }
2042
+ // Fall back to default renderer
2043
+ const defaultRenderer = base.renderers[blockName] || base.fallback;
2044
+ return defaultRenderer(props);
2045
+ };
2046
+ };
2047
+ // For each mapping, override the renderer for that block name
2048
+ for (const mapping of mappings) {
2049
+ const blockName = mapping.pattern.name;
2050
+ if (blockName) {
2051
+ enhancedRenderers[blockName] = createPatternRenderer(blockName);
2052
+ }
2053
+ }
2054
+ // Create matchBlock function
2055
+ const matchBlock = (block) => {
2056
+ return findMatchingMapping(block, mappings);
2057
+ };
2058
+ return {
2059
+ ...base,
2060
+ renderers: enhancedRenderers,
2061
+ mappings,
2062
+ matchBlock,
2063
+ };
2064
+ }
2065
+
1909
2066
  const WPContent = ({ blocks, registry, className }) => {
1910
2067
  if (!Array.isArray(blocks)) {
1911
2068
  if (process.env.NODE_ENV !== 'production') {
@@ -1939,7 +2096,7 @@ const WPPage = ({ page, registry, className }) => {
1939
2096
  }
1940
2097
  const hasHeroShortcode = useMemo(() => detectHeroShortcode(page.blocks), [page.blocks]);
1941
2098
  const featured = getFeaturedImage(page);
1942
- return (jsxRuntimeExports.jsxs("article", { className: className, children: [!hasHeroShortcode && featured && (jsxRuntimeExports.jsx(HeroFromFeatured, { featured: featured, title: page.title?.rendered })), jsxRuntimeExports.jsxs("div", { className: "container mx-auto px-4 py-8 prose max-w-none", children: [jsxRuntimeExports.jsx("header", { className: "mb-8", children: page.title?.rendered && (jsxRuntimeExports.jsx("h1", { className: "text-3xl font-bold", children: page.title.rendered })) }), jsxRuntimeExports.jsx(WPContent, { blocks: page.blocks, registry: registry })] })] }));
2099
+ return (jsxRuntimeExports.jsxs("article", { className: className, children: [!hasHeroShortcode && featured && (jsxRuntimeExports.jsx(HeroFromFeatured, { featured: featured, title: page.title?.rendered })), jsxRuntimeExports.jsx(WPContent, { blocks: page.blocks, registry: registry })] }));
1943
2100
  };
1944
2101
  function detectHeroShortcode(blocks) {
1945
2102
  for (const block of blocks) {
@@ -1986,5 +2143,252 @@ class WPErrorBoundary extends React.Component {
1986
2143
  }
1987
2144
  }
1988
2145
 
1989
- export { WPContent, WPErrorBoundary, WPPage, buildClassName, createDefaultRegistry, extractTextFromHTML, findShortcodes, getAlignmentClasses, getBlockTextContent, getCloudflareVariantUrl, getContainerClasses, getContentSpacingClasses, getFontSizeClasses, getImageAttributes, getImageUrl, getSectionSpacingClasses, getTextAlignClasses, isCloudflareImageUrl, parseGutenbergBlocks, parseShortcodeAttrs, renderNodes, renderTextWithShortcodes };
2146
+ /**
2147
+ * Extract background image URL from a block
2148
+ * Checks various possible sources: url, backgroundImage, innerHTML
2149
+ */
2150
+ function extractBackgroundImage(block) {
2151
+ const attrs = block.attributes || {};
2152
+ // Try various attribute keys
2153
+ let url = attrs['url'] ||
2154
+ attrs['backgroundImage'] ||
2155
+ (typeof attrs['backgroundImage'] === 'object' && attrs['backgroundImage']?.url);
2156
+ if (typeof url === 'string' && url.trim()) {
2157
+ return url.trim();
2158
+ }
2159
+ // Try to extract from innerHTML if not found in attributes
2160
+ if (block.innerHTML) {
2161
+ // Try img src from innerHTML
2162
+ const imgMatch = block.innerHTML.match(/<img[^>]+src=["']([^"']+)["']/i);
2163
+ if (imgMatch && imgMatch[1]) {
2164
+ return imgMatch[1];
2165
+ }
2166
+ // Try background-image in style attribute
2167
+ const bgMatch = block.innerHTML.match(/background-image:\s*url\(["']?([^"')]+)["']?\)/i);
2168
+ if (bgMatch && bgMatch[1]) {
2169
+ return bgMatch[1];
2170
+ }
2171
+ }
2172
+ return null;
2173
+ }
2174
+ /**
2175
+ * Extract image URL from a block
2176
+ * Returns Cloudflare URL if available, otherwise WordPress URL
2177
+ */
2178
+ function extractImageUrl(block) {
2179
+ return getImageUrl(block);
2180
+ }
2181
+ /**
2182
+ * Extract image attributes (url, alt, width, height)
2183
+ */
2184
+ function extractImageAttributes(block) {
2185
+ return getImageAttributes(block);
2186
+ }
2187
+ /**
2188
+ * Extract title/heading text from a block
2189
+ */
2190
+ function extractTitle(block) {
2191
+ const attrs = block.attributes || {};
2192
+ const title = attrs['title'] || attrs['content'] || getBlockTextContent(block);
2193
+ return typeof title === 'string' ? title.trim() : null;
2194
+ }
2195
+ /**
2196
+ * Extract content/text from a block
2197
+ * Returns React node for rendering
2198
+ */
2199
+ function extractContent(block, context) {
2200
+ const text = getBlockTextContent(block);
2201
+ return text || null;
2202
+ }
2203
+ /**
2204
+ * Extract media position from media-text block
2205
+ */
2206
+ function extractMediaPosition(block) {
2207
+ const attrs = block.attributes || {};
2208
+ const position = attrs['mediaPosition'] || 'left';
2209
+ return position === 'right' ? 'right' : 'left';
2210
+ }
2211
+ /**
2212
+ * Extract vertical alignment from block
2213
+ */
2214
+ function extractVerticalAlignment(block) {
2215
+ const attrs = block.attributes || {};
2216
+ const alignment = attrs['verticalAlignment'] || 'center';
2217
+ if (alignment === 'top' || alignment === 'bottom') {
2218
+ return alignment;
2219
+ }
2220
+ return 'center';
2221
+ }
2222
+ /**
2223
+ * Extract alignment (full, wide, contained) from block
2224
+ */
2225
+ function extractAlignment(block) {
2226
+ const attrs = block.attributes || {};
2227
+ const align = attrs['align'];
2228
+ if (align === 'full' || align === 'wide') {
2229
+ return align;
2230
+ }
2231
+ return 'contained';
2232
+ }
2233
+ /**
2234
+ * Extract overlay color from cover block
2235
+ */
2236
+ function extractOverlayColor(block) {
2237
+ const attrs = block.attributes || {};
2238
+ const overlayColor = attrs['overlayColor'];
2239
+ if (typeof overlayColor === 'string') {
2240
+ return overlayColor;
2241
+ }
2242
+ return null;
2243
+ }
2244
+ /**
2245
+ * Extract dim ratio (overlay opacity) from cover block
2246
+ */
2247
+ function extractDimRatio(block) {
2248
+ const attrs = block.attributes || {};
2249
+ const dimRatio = attrs['dimRatio'];
2250
+ if (typeof dimRatio === 'number') {
2251
+ return dimRatio;
2252
+ }
2253
+ return 0;
2254
+ }
2255
+ /**
2256
+ * Extract min height from block
2257
+ */
2258
+ function extractMinHeight(block) {
2259
+ const attrs = block.attributes || {};
2260
+ const minHeight = attrs['minHeight'];
2261
+ const minHeightUnit = attrs['minHeightUnit'] || 'vh';
2262
+ if (typeof minHeight === 'number') {
2263
+ return { value: minHeight, unit: minHeightUnit };
2264
+ }
2265
+ return null;
2266
+ }
2267
+ /**
2268
+ * Extract heading level from heading block
2269
+ */
2270
+ function extractHeadingLevel(block) {
2271
+ const attrs = block.attributes || {};
2272
+ const level = attrs['level'];
2273
+ if (typeof level === 'number' && level >= 1 && level <= 6) {
2274
+ return level;
2275
+ }
2276
+ return 2; // Default to h2
2277
+ }
2278
+ /**
2279
+ * Extract text alignment from block
2280
+ */
2281
+ function extractTextAlign(block) {
2282
+ const attrs = block.attributes || {};
2283
+ const align = attrs['align'] || attrs['textAlign'];
2284
+ if (align === 'left' || align === 'center' || align === 'right') {
2285
+ return align;
2286
+ }
2287
+ return null;
2288
+ }
2289
+ /**
2290
+ * Extract font size from block
2291
+ */
2292
+ function extractFontSize(block) {
2293
+ const attrs = block.attributes || {};
2294
+ const fontSize = attrs['fontSize'];
2295
+ return typeof fontSize === 'string' ? fontSize : null;
2296
+ }
2297
+ /**
2298
+ * Convert image URL to Cloudflare variant if it's a Cloudflare URL
2299
+ */
2300
+ function convertImageToCloudflareVariant(url, options = {}) {
2301
+ if (!url)
2302
+ return null;
2303
+ if (isCloudflareImageUrl(url)) {
2304
+ const width = options.width || 1024;
2305
+ const height = options.height;
2306
+ return getCloudflareVariantUrl(url, { width, height });
2307
+ }
2308
+ return url;
2309
+ }
2310
+
2311
+ /**
2312
+ * Convert image URL with optional Cloudflare variant transformation
2313
+ *
2314
+ * @param url - Image URL (WordPress or Cloudflare)
2315
+ * @param options - Conversion options
2316
+ * @returns Converted URL or original if conversion not applicable
2317
+ */
2318
+ function convertImageUrl(url, options = {}) {
2319
+ if (!url)
2320
+ return null;
2321
+ const { convertToCloudflare = true, defaultWidth = 1024, defaultHeight, forceCloudflare = false, } = options;
2322
+ // If already Cloudflare URL and conversion is enabled
2323
+ if (isCloudflareImageUrl(url)) {
2324
+ if (convertToCloudflare) {
2325
+ return getCloudflareVariantUrl(url, {
2326
+ width: defaultWidth,
2327
+ height: defaultHeight,
2328
+ });
2329
+ }
2330
+ return url;
2331
+ }
2332
+ // If force conversion is enabled (not recommended - requires WordPress plugin to provide Cloudflare URLs)
2333
+ if (forceCloudflare) {
2334
+ // This would require additional logic to map WordPress URLs to Cloudflare URLs
2335
+ // which should be handled by the WordPress plugin providing Cloudflare URLs in block data
2336
+ console.warn('forceCloudflare is enabled but URL is not Cloudflare. WordPress plugin should provide Cloudflare URLs in block metadata.');
2337
+ }
2338
+ // Return original URL if not Cloudflare
2339
+ return url;
2340
+ }
2341
+ /**
2342
+ * Batch convert multiple image URLs
2343
+ */
2344
+ function convertImageUrls(urls, options = {}) {
2345
+ return urls.map(url => convertImageUrl(url, options));
2346
+ }
2347
+
2348
+ /**
2349
+ * Generic section wrapper component for consistent spacing and layout
2350
+ *
2351
+ * Usage in component mappings:
2352
+ * ```ts
2353
+ * {
2354
+ * pattern: { name: 'core/cover' },
2355
+ * component: HeroSection,
2356
+ * wrapper: SectionWrapper,
2357
+ * extractProps: (block) => ({ ... })
2358
+ * }
2359
+ * ```
2360
+ */
2361
+ const SectionWrapper = ({ children, background = 'light', spacing = 'medium', container = 'contained', className, block, }) => {
2362
+ // Background classes
2363
+ const backgroundClasses = {
2364
+ light: 'bg-white',
2365
+ dark: 'bg-gray-900 text-white',
2366
+ transparent: 'bg-transparent',
2367
+ };
2368
+ // Spacing classes (vertical padding)
2369
+ const spacingClasses = {
2370
+ none: '',
2371
+ small: 'py-8 md:py-12',
2372
+ medium: 'py-16 md:py-24',
2373
+ large: 'py-24 md:py-32',
2374
+ };
2375
+ // Container classes
2376
+ const containerClasses = {
2377
+ full: 'w-full',
2378
+ wide: 'max-w-7xl mx-auto px-4',
2379
+ contained: 'container mx-auto px-4',
2380
+ };
2381
+ // Extract additional props from block if provided
2382
+ const blockAttrs = block?.attributes || {};
2383
+ const blockBackground = blockAttrs['backgroundColor'] || blockAttrs['background'];
2384
+ const blockSpacing = blockAttrs['spacing'];
2385
+ const blockContainer = blockAttrs['container'] || blockAttrs['align'];
2386
+ // Override with block attributes if present
2387
+ const finalBackground = blockBackground || background;
2388
+ const finalSpacing = blockSpacing || spacing;
2389
+ const finalContainer = blockContainer || container;
2390
+ return (jsxRuntimeExports.jsx("section", { className: buildClassName(backgroundClasses[finalBackground] || backgroundClasses.light, spacingClasses[finalSpacing] || spacingClasses.medium, containerClasses[finalContainer] || containerClasses.contained, className), children: children }));
2391
+ };
2392
+
2393
+ export { SectionWrapper, WPContent, WPErrorBoundary, WPPage, buildClassName, convertImageToCloudflareVariant, convertImageUrl, convertImageUrls, createDefaultRegistry, createEnhancedRegistry, extractAlignment, extractBackgroundImage, extractContent, extractDimRatio, extractFontSize, extractHeadingLevel, extractImageAttributes, extractImageUrl, extractMediaPosition, extractMinHeight, extractOverlayColor, extractTextAlign, extractTextFromHTML, extractTitle, extractVerticalAlignment, findMatchingMapping, findShortcodes, getAlignmentClasses, getBlockTextContent, getCloudflareVariantUrl, getContainerClasses, getContentSpacingClasses, getFontSizeClasses, getImageAttributes, getImageUrl, getSectionSpacingClasses, getTextAlignClasses, isCloudflareImageUrl, matchesPattern, parseGutenbergBlocks, parseShortcodeAttrs, renderNodes, renderTextWithShortcodes };
1990
2394
  //# sourceMappingURL=index.esm.js.map