@marvalt/wparser 0.1.0 → 0.1.4

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
@@ -1395,8 +1395,123 @@ function renderBlock(block, registry, key, options) {
1395
1395
  return jsxRuntimeExports.jsx(React.Fragment, { children: node }, key);
1396
1396
  }
1397
1397
 
1398
- const Paragraph = ({ block }) => {
1398
+ /**
1399
+ * Parse shortcode attributes from a string
1400
+ * Supports: [shortcode attr="value" attr2="value2"]
1401
+ */
1402
+ function parseShortcodeAttrs(attrString) {
1403
+ if (!attrString || !attrString.trim()) {
1404
+ return {};
1405
+ }
1406
+ const attrs = {};
1407
+ // Match key="value" or key='value' patterns
1408
+ const attrRegex = /(\w+)=["']([^"']+)["']/g;
1409
+ let match;
1410
+ while ((match = attrRegex.exec(attrString)) !== null) {
1411
+ attrs[match[1]] = match[2];
1412
+ }
1413
+ return attrs;
1414
+ }
1415
+ /**
1416
+ * Find all shortcodes in text
1417
+ * Supports: [shortcode], [shortcode attr="value"], [shortcode]content[/shortcode]
1418
+ */
1419
+ function findShortcodes(text) {
1420
+ const shortcodes = [];
1421
+ // First, find closing tag pairs: [shortcode]content[/shortcode]
1422
+ const closingTagRegex = /\[(\w+)(?:\s+([^\]]+))?\](.*?)\[\/\1\]/gs;
1423
+ let match;
1424
+ const processedRanges = [];
1425
+ while ((match = closingTagRegex.exec(text)) !== null) {
1426
+ const name = match[1];
1427
+ const attrString = match[2] || '';
1428
+ const content = match[3] || '';
1429
+ const attrs = parseShortcodeAttrs(attrString);
1430
+ shortcodes.push({
1431
+ name,
1432
+ attrs,
1433
+ content,
1434
+ fullMatch: match[0],
1435
+ startIndex: match.index,
1436
+ endIndex: match.index + match[0].length,
1437
+ });
1438
+ processedRanges.push({
1439
+ start: match.index,
1440
+ end: match.index + match[0].length,
1441
+ });
1442
+ }
1443
+ // Then, find self-closing: [shortcode attr="value"]
1444
+ // Skip ranges already processed by closing tags
1445
+ const selfClosingRegex = /\[(\w+)(?:\s+([^\]]+))?\]/g;
1446
+ let selfClosingMatch;
1447
+ while ((selfClosingMatch = selfClosingRegex.exec(text)) !== null) {
1448
+ // Check if this match is within a processed range
1449
+ const isProcessed = processedRanges.some(range => selfClosingMatch.index >= range.start && selfClosingMatch.index < range.end);
1450
+ if (isProcessed) {
1451
+ continue;
1452
+ }
1453
+ const name = selfClosingMatch[1];
1454
+ const attrString = selfClosingMatch[2] || '';
1455
+ const attrs = parseShortcodeAttrs(attrString);
1456
+ shortcodes.push({
1457
+ name,
1458
+ attrs,
1459
+ fullMatch: selfClosingMatch[0],
1460
+ startIndex: selfClosingMatch.index,
1461
+ endIndex: selfClosingMatch.index + selfClosingMatch[0].length,
1462
+ });
1463
+ }
1464
+ // Sort by start index
1465
+ shortcodes.sort((a, b) => a.startIndex - b.startIndex);
1466
+ return shortcodes;
1467
+ }
1468
+ /**
1469
+ * Render text with shortcodes replaced by React components
1470
+ */
1471
+ function renderTextWithShortcodes(text, registry) {
1472
+ const shortcodes = findShortcodes(text);
1473
+ if (shortcodes.length === 0) {
1474
+ return [text];
1475
+ }
1476
+ const parts = [];
1477
+ let lastIndex = 0;
1478
+ for (const shortcode of shortcodes) {
1479
+ // Add text before shortcode
1480
+ if (shortcode.startIndex > lastIndex) {
1481
+ const textBefore = text.substring(lastIndex, shortcode.startIndex);
1482
+ if (textBefore) {
1483
+ parts.push(textBefore);
1484
+ }
1485
+ }
1486
+ // Render shortcode
1487
+ const renderer = registry.shortcodes[shortcode.name];
1488
+ if (renderer) {
1489
+ parts.push(jsxRuntimeExports.jsx(React.Fragment, { children: renderer(shortcode.attrs, shortcode.content) }, `shortcode-${shortcode.startIndex}`));
1490
+ }
1491
+ else {
1492
+ // Keep original shortcode if no renderer found
1493
+ parts.push(shortcode.fullMatch);
1494
+ }
1495
+ lastIndex = shortcode.endIndex;
1496
+ }
1497
+ // Add remaining text
1498
+ if (lastIndex < text.length) {
1499
+ const remainingText = text.substring(lastIndex);
1500
+ if (remainingText) {
1501
+ parts.push(remainingText);
1502
+ }
1503
+ }
1504
+ return parts;
1505
+ }
1506
+
1507
+ const Paragraph = ({ block, context }) => {
1399
1508
  const content = getString(block);
1509
+ // Check if content contains shortcodes
1510
+ const hasShortcodes = /\[(\w+)/.test(content);
1511
+ if (hasShortcodes && context.registry.shortcodes) {
1512
+ const parts = renderTextWithShortcodes(content, context.registry);
1513
+ return jsxRuntimeExports.jsx("p", { className: "prose-p", children: parts });
1514
+ }
1400
1515
  return jsxRuntimeExports.jsx("p", { className: "prose-p", children: content });
1401
1516
  };
1402
1517
  const Heading = ({ block, children }) => {
@@ -1435,6 +1550,59 @@ const ButtonBlock = ({ block }) => {
1435
1550
  return null;
1436
1551
  return (jsxRuntimeExports.jsx("a", { href: url, className: "inline-flex items-center rounded-md bg-primary px-4 py-2 text-white", children: text || 'Learn more' }));
1437
1552
  };
1553
+ const Cover = ({ block, children }) => {
1554
+ const attrs = block.attributes || {};
1555
+ const { url, backgroundImage, overlayColor, dimRatio = 0, align = 'full', minHeight, hasParallax, } = attrs;
1556
+ // Get background image URL from various possible sources
1557
+ const bgImageUrl = url || backgroundImage || (typeof backgroundImage === 'object' && backgroundImage?.url);
1558
+ // Build alignment classes
1559
+ const alignClass = align === 'full' ? 'w-full' : align === 'wide' ? 'max-w-7xl mx-auto' : '';
1560
+ // Build style object
1561
+ const style = {};
1562
+ if (minHeight) {
1563
+ style.minHeight = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
1564
+ }
1565
+ if (bgImageUrl) {
1566
+ style.backgroundImage = `url(${bgImageUrl})`;
1567
+ style.backgroundSize = 'cover';
1568
+ style.backgroundPosition = 'center';
1569
+ if (hasParallax) {
1570
+ style.backgroundAttachment = 'fixed';
1571
+ }
1572
+ }
1573
+ // Calculate overlay opacity
1574
+ const overlayOpacity = dimRatio / 100;
1575
+ return (jsxRuntimeExports.jsxs("div", { className: `relative ${alignClass}`, style: style, children: [overlayOpacity > 0 && (jsxRuntimeExports.jsx("span", { className: "absolute inset-0", style: {
1576
+ backgroundColor: overlayColor || '#000000',
1577
+ opacity: overlayOpacity,
1578
+ }, "aria-hidden": "true" })), jsxRuntimeExports.jsx("div", { className: "relative z-10 container mx-auto px-4 py-12", children: children })] }));
1579
+ };
1580
+ const MediaText = ({ block, children, context }) => {
1581
+ const attrs = block.attributes || {};
1582
+ const { mediaPosition = 'left', verticalAlignment = 'center', imageFill = false, align = 'wide', } = attrs;
1583
+ // Access innerBlocks to identify media vs content
1584
+ const innerBlocks = block.innerBlocks || [];
1585
+ // Find media block (image or video)
1586
+ const mediaBlockIndex = innerBlocks.findIndex((b) => b.name === 'core/image' || b.name === 'core/video');
1587
+ // Render children - media-text typically has media as first child, then content
1588
+ const childrenArray = React.Children.toArray(children);
1589
+ const mediaElement = mediaBlockIndex >= 0 && childrenArray[mediaBlockIndex]
1590
+ ? childrenArray[mediaBlockIndex]
1591
+ : null;
1592
+ // Content is all other children
1593
+ const contentElements = childrenArray.filter((_, index) => index !== mediaBlockIndex);
1594
+ // Build alignment classes
1595
+ const alignClass = align === 'full' ? 'w-full' : align === 'wide' ? 'max-w-7xl mx-auto' : 'max-w-6xl mx-auto';
1596
+ // Vertical alignment classes
1597
+ const verticalAlignClass = verticalAlignment === 'top' ? 'items-start' :
1598
+ verticalAlignment === 'bottom' ? 'items-end' :
1599
+ 'items-center';
1600
+ // Stack on mobile
1601
+ const stackClass = 'flex-col md:flex-row';
1602
+ // Media position determines order
1603
+ const isMediaRight = mediaPosition === 'right';
1604
+ return (jsxRuntimeExports.jsx("div", { className: `${alignClass} px-4`, children: jsxRuntimeExports.jsxs("div", { className: `flex ${stackClass} ${verticalAlignClass} gap-6`, children: [jsxRuntimeExports.jsx("div", { className: `${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" }) }), jsxRuntimeExports.jsx("div", { className: `${isMediaRight ? 'order-1' : 'order-2'} ${imageFill ? 'w-full md:w-1/2' : 'flex-1'} space-y-4`, children: contentElements.length > 0 ? contentElements : children })] }) }));
1605
+ };
1438
1606
  const Fallback = ({ block, children }) => {
1439
1607
  // Minimal fallback; do not render innerHTML directly in v1 for safety
1440
1608
  return jsxRuntimeExports.jsx("div", { "data-unknown-block": block.name, children: children });
@@ -1458,9 +1626,20 @@ function createDefaultRegistry() {
1458
1626
  'core/table': ({ children }) => jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: jsxRuntimeExports.jsx("table", { className: "table-auto w-full", children: children }) }),
1459
1627
  'core/table-row': ({ children }) => jsxRuntimeExports.jsx("tr", { children: children }),
1460
1628
  'core/table-cell': ({ children }) => jsxRuntimeExports.jsx("td", { className: "border px-3 py-2", children: children }),
1629
+ // Cover block - hero sections with background images
1630
+ 'core/cover': Cover,
1631
+ // Media & Text block - side-by-side media and content
1632
+ 'core/media-text': MediaText,
1633
+ // HTML block - render innerHTML as-is
1634
+ // Note: Shortcodes in HTML blocks are not parsed (they would need to be in text content)
1635
+ 'core/html': ({ block }) => {
1636
+ const html = block.innerHTML || '';
1637
+ return jsxRuntimeExports.jsx("div", { dangerouslySetInnerHTML: { __html: html } });
1638
+ },
1461
1639
  };
1462
1640
  return {
1463
1641
  renderers,
1642
+ shortcodes: {}, // Empty by default - apps extend this
1464
1643
  fallback: Fallback,
1465
1644
  };
1466
1645
  }
@@ -1552,5 +1731,5 @@ class WPErrorBoundary extends React.Component {
1552
1731
  }
1553
1732
  }
1554
1733
 
1555
- export { WPContent, WPErrorBoundary, WPPage, createDefaultRegistry, parseGutenbergBlocks, renderNodes };
1734
+ export { WPContent, WPErrorBoundary, WPPage, createDefaultRegistry, findShortcodes, parseGutenbergBlocks, parseShortcodeAttrs, renderNodes, renderTextWithShortcodes };
1556
1735
  //# sourceMappingURL=index.esm.js.map