@marvalt/wparser 0.1.60 → 0.1.62

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
@@ -1406,6 +1406,115 @@ function renderBlock(block, registry, key, options, page) {
1406
1406
  return jsxRuntimeExports.jsx(React.Fragment, { children: node }, key);
1407
1407
  }
1408
1408
 
1409
+ /**
1410
+ * Parse shortcode attributes from a string
1411
+ * Supports: [shortcode attr="value" attr2="value2"]
1412
+ */
1413
+ function parseShortcodeAttrs(attrString) {
1414
+ if (!attrString || !attrString.trim()) {
1415
+ return {};
1416
+ }
1417
+ const attrs = {};
1418
+ // Match key="value" or key='value' patterns
1419
+ const attrRegex = /(\w+)=["']([^"']+)["']/g;
1420
+ let match;
1421
+ while ((match = attrRegex.exec(attrString)) !== null) {
1422
+ attrs[match[1]] = match[2];
1423
+ }
1424
+ return attrs;
1425
+ }
1426
+ /**
1427
+ * Find all shortcodes in text
1428
+ * Supports: [shortcode], [shortcode attr="value"], [shortcode]content[/shortcode]
1429
+ */
1430
+ function findShortcodes(text) {
1431
+ const shortcodes = [];
1432
+ // First, find closing tag pairs: [shortcode]content[/shortcode]
1433
+ const closingTagRegex = /\[(\w+)(?:\s+([^\]]+))?\](.*?)\[\/\1\]/gs;
1434
+ let match;
1435
+ const processedRanges = [];
1436
+ while ((match = closingTagRegex.exec(text)) !== null) {
1437
+ const name = match[1];
1438
+ const attrString = match[2] || '';
1439
+ const content = match[3] || '';
1440
+ const attrs = parseShortcodeAttrs(attrString);
1441
+ shortcodes.push({
1442
+ name,
1443
+ attrs,
1444
+ content,
1445
+ fullMatch: match[0],
1446
+ startIndex: match.index,
1447
+ endIndex: match.index + match[0].length,
1448
+ });
1449
+ processedRanges.push({
1450
+ start: match.index,
1451
+ end: match.index + match[0].length,
1452
+ });
1453
+ }
1454
+ // Then, find self-closing: [shortcode attr="value"]
1455
+ // Skip ranges already processed by closing tags
1456
+ const selfClosingRegex = /\[(\w+)(?:\s+([^\]]+))?\]/g;
1457
+ let selfClosingMatch;
1458
+ while ((selfClosingMatch = selfClosingRegex.exec(text)) !== null) {
1459
+ // Check if this match is within a processed range
1460
+ const isProcessed = processedRanges.some(range => selfClosingMatch.index >= range.start && selfClosingMatch.index < range.end);
1461
+ if (isProcessed) {
1462
+ continue;
1463
+ }
1464
+ const name = selfClosingMatch[1];
1465
+ const attrString = selfClosingMatch[2] || '';
1466
+ const attrs = parseShortcodeAttrs(attrString);
1467
+ shortcodes.push({
1468
+ name,
1469
+ attrs,
1470
+ fullMatch: selfClosingMatch[0],
1471
+ startIndex: selfClosingMatch.index,
1472
+ endIndex: selfClosingMatch.index + selfClosingMatch[0].length,
1473
+ });
1474
+ }
1475
+ // Sort by start index
1476
+ shortcodes.sort((a, b) => a.startIndex - b.startIndex);
1477
+ return shortcodes;
1478
+ }
1479
+ /**
1480
+ * Render text with shortcodes replaced by React components
1481
+ */
1482
+ function renderTextWithShortcodes(text, registry) {
1483
+ const shortcodes = findShortcodes(text);
1484
+ if (shortcodes.length === 0) {
1485
+ return [text];
1486
+ }
1487
+ const parts = [];
1488
+ let lastIndex = 0;
1489
+ for (const shortcode of shortcodes) {
1490
+ // Add text before shortcode
1491
+ if (shortcode.startIndex > lastIndex) {
1492
+ const textBefore = text.substring(lastIndex, shortcode.startIndex);
1493
+ if (textBefore) {
1494
+ parts.push(textBefore);
1495
+ }
1496
+ }
1497
+ // Render shortcode
1498
+ const renderer = registry.shortcodes[shortcode.name];
1499
+ if (renderer) {
1500
+ parts.push(jsxRuntimeExports.jsx(React.Fragment, { children: renderer(shortcode.attrs, shortcode.content) }, `shortcode-${shortcode.startIndex}`));
1501
+ }
1502
+ else {
1503
+ // Keep original shortcode if no renderer found
1504
+ parts.push(shortcode.fullMatch);
1505
+ }
1506
+ lastIndex = shortcode.endIndex;
1507
+ }
1508
+ // Add remaining text
1509
+ if (lastIndex < text.length) {
1510
+ const remainingText = text.substring(lastIndex);
1511
+ if (remainingText) {
1512
+ parts.push(remainingText);
1513
+ }
1514
+ }
1515
+ return parts;
1516
+ }
1517
+
1409
1518
  /**
1410
1519
  * Content extraction utilities for WordPress blocks
1411
1520
  * Extracts text content from various block formats
@@ -2149,6 +2258,10 @@ function extractSpacerHeight(block) {
2149
2258
  return height;
2150
2259
  }
2151
2260
  if (typeof height === 'string') {
2261
+ // If it's a CSS var or clamp, return as-is
2262
+ if (height.startsWith('var(') || height.includes('clamp(')) {
2263
+ return height;
2264
+ }
2152
2265
  // Parse "100px" or "100" to number
2153
2266
  const match = height.match(/^(\d+)/);
2154
2267
  if (match) {
@@ -2159,9 +2272,17 @@ function extractSpacerHeight(block) {
2159
2272
  if (block.innerHTML) {
2160
2273
  const styleMatch = block.innerHTML.match(/style=["']([^"']+)["']/i);
2161
2274
  if (styleMatch) {
2162
- const heightMatch = styleMatch[1].match(/height:\s*(\d+)px/i);
2163
- if (heightMatch) {
2164
- return parseInt(heightMatch[1], 10);
2275
+ // First look for CSS var or clamp
2276
+ const cssVarMatch = styleMatch[1].match(/height:\s*([^;]+);?/i);
2277
+ if (cssVarMatch) {
2278
+ const cssHeight = cssVarMatch[1].trim();
2279
+ if (cssHeight.startsWith('var(') || cssHeight.includes('clamp(')) {
2280
+ return cssHeight;
2281
+ }
2282
+ const numericMatch = cssHeight.match(/^(\d+)/);
2283
+ if (numericMatch) {
2284
+ return parseInt(numericMatch[1], 10);
2285
+ }
2165
2286
  }
2166
2287
  }
2167
2288
  }
@@ -2265,24 +2386,80 @@ function buildClassName(...classes) {
2265
2386
  function getSpacing(spacingConfig, key, fallback) {
2266
2387
  return spacingConfig?.[key] || fallback;
2267
2388
  }
2268
- const Paragraph = ({ block }) => {
2269
- // If innerHTML exists, render it as-is to preserve WP classes/styles
2270
- if (block.innerHTML) {
2271
- return jsxRuntimeExports.jsx("div", { dangerouslySetInnerHTML: { __html: block.innerHTML } });
2272
- }
2389
+ // Helper function to get heading spacing
2390
+ function getHeadingSpacing(spacingConfig, level) {
2391
+ const headingConfig = spacingConfig?.heading;
2392
+ if (level === 1)
2393
+ return headingConfig?.h1 || 'mt-10 mb-8';
2394
+ if (level === 2)
2395
+ return headingConfig?.h2 || 'mt-8 mb-6';
2396
+ return headingConfig?.h3 || 'mt-6 mb-4';
2397
+ }
2398
+ const Paragraph = ({ block, context }) => {
2399
+ const attrs = block.attributes || {};
2400
+ const textAlign = getTextAlignClasses(attrs['align']);
2401
+ const spacing = getSpacing(context.spacingConfig || context.registry.spacingConfig, 'paragraph', 'my-6');
2402
+ // Extract text color if specified, otherwise inherit from parent (CSS variables handle default)
2403
+ const textColor = extractTextColor(block, context);
2404
+ const textColorClass = textColor || '';
2405
+ // Extract font size if specified
2406
+ const fontSize = extractFontSize(block, context);
2407
+ const fontSizeClass = fontSize ? getFontSizeClasses(fontSize) : '';
2408
+ const hasHTML = block.innerHTML && /<[a-z][\s\S]*>/i.test(block.innerHTML);
2409
+ const htmlContent = block.innerHTML || '';
2273
2410
  const textContent = getBlockTextContent(block);
2274
- return jsxRuntimeExports.jsx("p", { children: textContent });
2275
- };
2276
- const Heading = ({ block, children }) => {
2277
- // If innerHTML exists, render it as-is to preserve WP classes/styles (font sizes, weights)
2278
- if (block.innerHTML) {
2279
- return jsxRuntimeExports.jsx("div", { dangerouslySetInnerHTML: { __html: block.innerHTML } });
2411
+ const hasShortcodes = /\[(\w+)/.test(htmlContent) || /\[(\w+)/.test(textContent);
2412
+ if (hasShortcodes && context.registry.shortcodes) {
2413
+ const parts = renderTextWithShortcodes(textContent, context.registry);
2414
+ const isBlockLevelElement = (element) => {
2415
+ if (!React.isValidElement(element))
2416
+ return false;
2417
+ const type = element.type;
2418
+ if (typeof type === 'string' && ['section', 'div', 'article', 'header', 'footer', 'aside', 'nav'].includes(type))
2419
+ return true;
2420
+ if (type === React.Fragment) {
2421
+ const children = React.Children.toArray(element.props.children);
2422
+ return children.some((child) => isBlockLevelElement(child));
2423
+ }
2424
+ if (typeof type === 'function' || (typeof type === 'object' && type !== null && type !== React.Fragment))
2425
+ return true;
2426
+ return false;
2427
+ };
2428
+ const hasBlockLevelContent = React.Children.toArray(parts).some((part) => isBlockLevelElement(part));
2429
+ if (hasBlockLevelContent) {
2430
+ return jsxRuntimeExports.jsx("div", { className: buildClassName(spacing, textColorClass, fontSizeClass), children: parts });
2431
+ }
2432
+ return jsxRuntimeExports.jsx("p", { className: buildClassName(spacing, textAlign, textColorClass, fontSizeClass), children: parts });
2433
+ }
2434
+ if (hasHTML && block.innerHTML) {
2435
+ const trimmedContent = htmlContent.trim();
2436
+ if (trimmedContent.startsWith('<p') && trimmedContent.endsWith('</p>')) {
2437
+ const contentMatch = trimmedContent.match(/<p[^>]*>(.*?)<\/p>/s);
2438
+ if (contentMatch) {
2439
+ const innerContent = contentMatch[1];
2440
+ if (innerContent.trim().startsWith('<p')) {
2441
+ return jsxRuntimeExports.jsx("div", { className: buildClassName(spacing, textAlign, textColorClass, fontSizeClass), dangerouslySetInnerHTML: { __html: innerContent } });
2442
+ }
2443
+ return jsxRuntimeExports.jsx("p", { className: buildClassName(spacing, textAlign, textColorClass, fontSizeClass), dangerouslySetInnerHTML: { __html: innerContent } });
2444
+ }
2445
+ }
2446
+ return jsxRuntimeExports.jsx("p", { className: buildClassName(spacing, textAlign, textColorClass, fontSizeClass), dangerouslySetInnerHTML: { __html: htmlContent } });
2280
2447
  }
2448
+ return jsxRuntimeExports.jsx("p", { className: buildClassName(spacing, textAlign, textColorClass, fontSizeClass), children: textContent });
2449
+ };
2450
+ const Heading = ({ block, children, context }) => {
2281
2451
  const attrs = block.attributes || {};
2282
2452
  const { level = 2 } = attrs;
2283
2453
  const content = getBlockTextContent(block);
2454
+ const textAlign = getTextAlignClasses(attrs['textAlign']);
2455
+ const extractedFontSize = extractFontSize(block, context);
2456
+ const fontSize = extractedFontSize ? getFontSizeClasses(extractedFontSize) : '';
2284
2457
  const Tag = `h${Math.min(Math.max(Number(level) || 2, 1), 6)}`;
2285
- return jsxRuntimeExports.jsx(Tag, { children: children ?? content });
2458
+ const sizeClass = fontSize || '';
2459
+ const spacingClass = getHeadingSpacing(context.spacingConfig || context.registry.spacingConfig, level);
2460
+ const textColor = extractTextColor(block, context);
2461
+ const textColorClass = textColor || '';
2462
+ return (jsxRuntimeExports.jsx(Tag, { className: buildClassName(textColorClass, sizeClass, textAlign, spacingClass), children: children ?? content }));
2286
2463
  };
2287
2464
  const Image = ({ block, context }) => {
2288
2465
  const imageAttrs = getImageAttributes(block);
@@ -2656,7 +2833,10 @@ function createDefaultRegistry(colorMapper, spacingConfig) {
2656
2833
  // Spacer block - adds vertical spacing
2657
2834
  'core/spacer': ({ block }) => {
2658
2835
  const height = extractSpacerHeight(block);
2659
- if (height && height > 0) {
2836
+ if (typeof height === 'string') {
2837
+ return jsxRuntimeExports.jsx("div", { style: { height }, "aria-hidden": "true" });
2838
+ }
2839
+ if (typeof height === 'number' && height > 0) {
2660
2840
  return jsxRuntimeExports.jsx("div", { style: { height: `${height}px` }, "aria-hidden": "true" });
2661
2841
  }
2662
2842
  // Default fallback if height not found
@@ -2945,115 +3125,6 @@ class WPErrorBoundary extends React.Component {
2945
3125
  }
2946
3126
  }
2947
3127
 
2948
- /**
2949
- * Parse shortcode attributes from a string
2950
- * Supports: [shortcode attr="value" attr2="value2"]
2951
- */
2952
- function parseShortcodeAttrs(attrString) {
2953
- if (!attrString || !attrString.trim()) {
2954
- return {};
2955
- }
2956
- const attrs = {};
2957
- // Match key="value" or key='value' patterns
2958
- const attrRegex = /(\w+)=["']([^"']+)["']/g;
2959
- let match;
2960
- while ((match = attrRegex.exec(attrString)) !== null) {
2961
- attrs[match[1]] = match[2];
2962
- }
2963
- return attrs;
2964
- }
2965
- /**
2966
- * Find all shortcodes in text
2967
- * Supports: [shortcode], [shortcode attr="value"], [shortcode]content[/shortcode]
2968
- */
2969
- function findShortcodes(text) {
2970
- const shortcodes = [];
2971
- // First, find closing tag pairs: [shortcode]content[/shortcode]
2972
- const closingTagRegex = /\[(\w+)(?:\s+([^\]]+))?\](.*?)\[\/\1\]/gs;
2973
- let match;
2974
- const processedRanges = [];
2975
- while ((match = closingTagRegex.exec(text)) !== null) {
2976
- const name = match[1];
2977
- const attrString = match[2] || '';
2978
- const content = match[3] || '';
2979
- const attrs = parseShortcodeAttrs(attrString);
2980
- shortcodes.push({
2981
- name,
2982
- attrs,
2983
- content,
2984
- fullMatch: match[0],
2985
- startIndex: match.index,
2986
- endIndex: match.index + match[0].length,
2987
- });
2988
- processedRanges.push({
2989
- start: match.index,
2990
- end: match.index + match[0].length,
2991
- });
2992
- }
2993
- // Then, find self-closing: [shortcode attr="value"]
2994
- // Skip ranges already processed by closing tags
2995
- const selfClosingRegex = /\[(\w+)(?:\s+([^\]]+))?\]/g;
2996
- let selfClosingMatch;
2997
- while ((selfClosingMatch = selfClosingRegex.exec(text)) !== null) {
2998
- // Check if this match is within a processed range
2999
- const isProcessed = processedRanges.some(range => selfClosingMatch.index >= range.start && selfClosingMatch.index < range.end);
3000
- if (isProcessed) {
3001
- continue;
3002
- }
3003
- const name = selfClosingMatch[1];
3004
- const attrString = selfClosingMatch[2] || '';
3005
- const attrs = parseShortcodeAttrs(attrString);
3006
- shortcodes.push({
3007
- name,
3008
- attrs,
3009
- fullMatch: selfClosingMatch[0],
3010
- startIndex: selfClosingMatch.index,
3011
- endIndex: selfClosingMatch.index + selfClosingMatch[0].length,
3012
- });
3013
- }
3014
- // Sort by start index
3015
- shortcodes.sort((a, b) => a.startIndex - b.startIndex);
3016
- return shortcodes;
3017
- }
3018
- /**
3019
- * Render text with shortcodes replaced by React components
3020
- */
3021
- function renderTextWithShortcodes(text, registry) {
3022
- const shortcodes = findShortcodes(text);
3023
- if (shortcodes.length === 0) {
3024
- return [text];
3025
- }
3026
- const parts = [];
3027
- let lastIndex = 0;
3028
- for (const shortcode of shortcodes) {
3029
- // Add text before shortcode
3030
- if (shortcode.startIndex > lastIndex) {
3031
- const textBefore = text.substring(lastIndex, shortcode.startIndex);
3032
- if (textBefore) {
3033
- parts.push(textBefore);
3034
- }
3035
- }
3036
- // Render shortcode
3037
- const renderer = registry.shortcodes[shortcode.name];
3038
- if (renderer) {
3039
- parts.push(jsxRuntimeExports.jsx(React.Fragment, { children: renderer(shortcode.attrs, shortcode.content) }, `shortcode-${shortcode.startIndex}`));
3040
- }
3041
- else {
3042
- // Keep original shortcode if no renderer found
3043
- parts.push(shortcode.fullMatch);
3044
- }
3045
- lastIndex = shortcode.endIndex;
3046
- }
3047
- // Add remaining text
3048
- if (lastIndex < text.length) {
3049
- const remainingText = text.substring(lastIndex);
3050
- if (remainingText) {
3051
- parts.push(remainingText);
3052
- }
3053
- }
3054
- return parts;
3055
- }
3056
-
3057
3128
  /**
3058
3129
  * Convert image URL with optional Cloudflare variant transformation
3059
3130
  *