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