@marvalt/wparser 0.1.61 → 0.1.64

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
@@ -2277,24 +2386,80 @@ function buildClassName(...classes) {
2277
2386
  function getSpacing(spacingConfig, key, fallback) {
2278
2387
  return spacingConfig?.[key] || fallback;
2279
2388
  }
2280
- const Paragraph = ({ block }) => {
2281
- // If innerHTML exists, render it as-is to preserve WP classes/styles
2282
- if (block.innerHTML) {
2283
- return jsxRuntimeExports.jsx("div", { dangerouslySetInnerHTML: { __html: block.innerHTML } });
2284
- }
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 || '';
2285
2410
  const textContent = getBlockTextContent(block);
2286
- return jsxRuntimeExports.jsx("p", { children: textContent });
2287
- };
2288
- const Heading = ({ block, children }) => {
2289
- // If innerHTML exists, render it as-is to preserve WP classes/styles (font sizes, weights)
2290
- if (block.innerHTML) {
2291
- 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 } });
2292
2447
  }
2448
+ return jsxRuntimeExports.jsx("p", { className: buildClassName(spacing, textAlign, textColorClass, fontSizeClass), children: textContent });
2449
+ };
2450
+ const Heading = ({ block, children, context }) => {
2293
2451
  const attrs = block.attributes || {};
2294
2452
  const { level = 2 } = attrs;
2295
2453
  const content = getBlockTextContent(block);
2454
+ const textAlign = getTextAlignClasses(attrs['textAlign']);
2455
+ const extractedFontSize = extractFontSize(block, context);
2456
+ const fontSize = extractedFontSize ? getFontSizeClasses(extractedFontSize) : '';
2296
2457
  const Tag = `h${Math.min(Math.max(Number(level) || 2, 1), 6)}`;
2297
- 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 }));
2298
2463
  };
2299
2464
  const Image = ({ block, context }) => {
2300
2465
  const imageAttrs = getImageAttributes(block);
@@ -2960,115 +3125,6 @@ class WPErrorBoundary extends React.Component {
2960
3125
  }
2961
3126
  }
2962
3127
 
2963
- /**
2964
- * Parse shortcode attributes from a string
2965
- * Supports: [shortcode attr="value" attr2="value2"]
2966
- */
2967
- function parseShortcodeAttrs(attrString) {
2968
- if (!attrString || !attrString.trim()) {
2969
- return {};
2970
- }
2971
- const attrs = {};
2972
- // Match key="value" or key='value' patterns
2973
- const attrRegex = /(\w+)=["']([^"']+)["']/g;
2974
- let match;
2975
- while ((match = attrRegex.exec(attrString)) !== null) {
2976
- attrs[match[1]] = match[2];
2977
- }
2978
- return attrs;
2979
- }
2980
- /**
2981
- * Find all shortcodes in text
2982
- * Supports: [shortcode], [shortcode attr="value"], [shortcode]content[/shortcode]
2983
- */
2984
- function findShortcodes(text) {
2985
- const shortcodes = [];
2986
- // First, find closing tag pairs: [shortcode]content[/shortcode]
2987
- const closingTagRegex = /\[(\w+)(?:\s+([^\]]+))?\](.*?)\[\/\1\]/gs;
2988
- let match;
2989
- const processedRanges = [];
2990
- while ((match = closingTagRegex.exec(text)) !== null) {
2991
- const name = match[1];
2992
- const attrString = match[2] || '';
2993
- const content = match[3] || '';
2994
- const attrs = parseShortcodeAttrs(attrString);
2995
- shortcodes.push({
2996
- name,
2997
- attrs,
2998
- content,
2999
- fullMatch: match[0],
3000
- startIndex: match.index,
3001
- endIndex: match.index + match[0].length,
3002
- });
3003
- processedRanges.push({
3004
- start: match.index,
3005
- end: match.index + match[0].length,
3006
- });
3007
- }
3008
- // Then, find self-closing: [shortcode attr="value"]
3009
- // Skip ranges already processed by closing tags
3010
- const selfClosingRegex = /\[(\w+)(?:\s+([^\]]+))?\]/g;
3011
- let selfClosingMatch;
3012
- while ((selfClosingMatch = selfClosingRegex.exec(text)) !== null) {
3013
- // Check if this match is within a processed range
3014
- const isProcessed = processedRanges.some(range => selfClosingMatch.index >= range.start && selfClosingMatch.index < range.end);
3015
- if (isProcessed) {
3016
- continue;
3017
- }
3018
- const name = selfClosingMatch[1];
3019
- const attrString = selfClosingMatch[2] || '';
3020
- const attrs = parseShortcodeAttrs(attrString);
3021
- shortcodes.push({
3022
- name,
3023
- attrs,
3024
- fullMatch: selfClosingMatch[0],
3025
- startIndex: selfClosingMatch.index,
3026
- endIndex: selfClosingMatch.index + selfClosingMatch[0].length,
3027
- });
3028
- }
3029
- // Sort by start index
3030
- shortcodes.sort((a, b) => a.startIndex - b.startIndex);
3031
- return shortcodes;
3032
- }
3033
- /**
3034
- * Render text with shortcodes replaced by React components
3035
- */
3036
- function renderTextWithShortcodes(text, registry) {
3037
- const shortcodes = findShortcodes(text);
3038
- if (shortcodes.length === 0) {
3039
- return [text];
3040
- }
3041
- const parts = [];
3042
- let lastIndex = 0;
3043
- for (const shortcode of shortcodes) {
3044
- // Add text before shortcode
3045
- if (shortcode.startIndex > lastIndex) {
3046
- const textBefore = text.substring(lastIndex, shortcode.startIndex);
3047
- if (textBefore) {
3048
- parts.push(textBefore);
3049
- }
3050
- }
3051
- // Render shortcode
3052
- const renderer = registry.shortcodes[shortcode.name];
3053
- if (renderer) {
3054
- parts.push(jsxRuntimeExports.jsx(React.Fragment, { children: renderer(shortcode.attrs, shortcode.content) }, `shortcode-${shortcode.startIndex}`));
3055
- }
3056
- else {
3057
- // Keep original shortcode if no renderer found
3058
- parts.push(shortcode.fullMatch);
3059
- }
3060
- lastIndex = shortcode.endIndex;
3061
- }
3062
- // Add remaining text
3063
- if (lastIndex < text.length) {
3064
- const remainingText = text.substring(lastIndex);
3065
- if (remainingText) {
3066
- parts.push(remainingText);
3067
- }
3068
- }
3069
- return parts;
3070
- }
3071
-
3072
3128
  /**
3073
3129
  * Convert image URL with optional Cloudflare variant transformation
3074
3130
  *