@officexapp/catalogs-cli 0.2.6 → 0.2.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.js +298 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -853,20 +853,64 @@ function buildPreviewHtml(schema, port) {
|
|
|
853
853
|
.dev-banner .stub-tags { margin-left: auto; display: flex; gap: 6px; }
|
|
854
854
|
.dev-banner .stub-tag { background: rgba(255,255,255,0.1); border-radius: 3px; padding: 1px 6px; font-size: 11px; color: #fbbf24; }
|
|
855
855
|
|
|
856
|
-
/*
|
|
857
|
-
.
|
|
858
|
-
position: fixed;
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
856
|
+
/* Pages mindmap overlay */
|
|
857
|
+
.pages-overlay {
|
|
858
|
+
position: fixed; inset: 0; z-index: 99990; background: rgba(10,10,20,0.92);
|
|
859
|
+
backdrop-filter: blur(8px); display: none; align-items: center; justify-content: center;
|
|
860
|
+
font-family: var(--font-display);
|
|
861
|
+
}
|
|
862
|
+
.pages-overlay.open { display: flex; }
|
|
863
|
+
.pages-overlay .close-btn {
|
|
864
|
+
position: absolute; top: 16px; right: 16px; background: rgba(255,255,255,0.1);
|
|
865
|
+
border: none; color: white; width: 32px; height: 32px; border-radius: 8px;
|
|
866
|
+
cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center;
|
|
867
|
+
}
|
|
868
|
+
.pages-overlay .close-btn:hover { background: rgba(255,255,255,0.2); }
|
|
869
|
+
.mindmap-container { position: relative; padding: 40px; }
|
|
870
|
+
.mindmap-container svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
|
|
871
|
+
.mindmap-nodes { position: relative; display: flex; flex-wrap: wrap; gap: 24px; justify-content: center; align-items: flex-start; max-width: 900px; }
|
|
872
|
+
.mindmap-node {
|
|
873
|
+
background: rgba(255,255,255,0.08); border: 1.5px solid rgba(255,255,255,0.15);
|
|
874
|
+
border-radius: 14px; padding: 14px 20px; min-width: 140px; text-align: center;
|
|
875
|
+
cursor: pointer; transition: all 0.2s ease; position: relative;
|
|
862
876
|
}
|
|
863
|
-
.
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
877
|
+
.mindmap-node:hover { background: rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.3); transform: translateY(-2px); }
|
|
878
|
+
.mindmap-node.entry { border-color: #4ade80; box-shadow: 0 0 0 3px rgba(74,222,128,0.15); }
|
|
879
|
+
.mindmap-node.current { border-color: var(--theme-color); box-shadow: 0 0 0 3px rgba(99,102,241,0.25); }
|
|
880
|
+
.mindmap-node .node-title { color: white; font-size: 13px; font-weight: 600; margin-bottom: 2px; }
|
|
881
|
+
.mindmap-node .node-id { color: rgba(255,255,255,0.35); font-size: 10px; font-family: monospace; }
|
|
882
|
+
.mindmap-node .node-badge {
|
|
883
|
+
position: absolute; top: -8px; right: -8px; background: #4ade80; color: #0a2010;
|
|
884
|
+
font-size: 9px; font-weight: 700; padding: 1px 6px; border-radius: 6px; text-transform: uppercase;
|
|
885
|
+
}
|
|
886
|
+
.mindmap-node .node-components { color: rgba(255,255,255,0.3); font-size: 10px; margin-top: 4px; }
|
|
887
|
+
.mindmap-edge-label {
|
|
888
|
+
position: absolute; background: rgba(251,191,36,0.15); color: #fbbf24;
|
|
889
|
+
font-size: 9px; padding: 1px 6px; border-radius: 4px; white-space: nowrap; pointer-events: none;
|
|
890
|
+
}
|
|
891
|
+
.dev-banner .stub-tag.clickable { cursor: pointer; transition: all 0.15s ease; }
|
|
892
|
+
.dev-banner .stub-tag.clickable:hover { background: rgba(255,255,255,0.2); color: #a5b4fc; }
|
|
893
|
+
|
|
894
|
+
/* Element inspector (local dev) */
|
|
895
|
+
.inspector-highlight {
|
|
896
|
+
position: fixed; z-index: 99997; pointer-events: none; border: 2px solid #6366f1;
|
|
897
|
+
background: rgba(99,102,241,0.06); border-radius: 8px; transition: all 80ms ease-out;
|
|
898
|
+
}
|
|
899
|
+
.inspector-tooltip {
|
|
900
|
+
position: fixed; z-index: 99998; pointer-events: none; background: #1e1b4b;
|
|
901
|
+
color: #e0e7ff; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px;
|
|
902
|
+
padding: 6px 10px; border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
|
903
|
+
max-width: 360px;
|
|
904
|
+
}
|
|
905
|
+
.inspector-tooltip .ref { font-weight: 700; color: #a5b4fc; }
|
|
906
|
+
.inspector-tooltip .type { color: rgba(165,180,252,0.5); margin-left: 6px; }
|
|
907
|
+
.inspector-tooltip .hint { color: rgba(165,180,252,0.3); font-size: 9px; margin-top: 3px; }
|
|
908
|
+
.inspector-active-banner {
|
|
909
|
+
position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 99998;
|
|
910
|
+
background: rgba(99,102,241,0.92); color: white; font-size: 11px; font-weight: 600;
|
|
911
|
+
padding: 6px 14px; border-radius: 10px; font-family: system-ui; pointer-events: none;
|
|
912
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
|
867
913
|
}
|
|
868
|
-
.dev-page-nav button:hover { color: white; background: rgba(255,255,255,0.1); }
|
|
869
|
-
.dev-page-nav button.active { color: white; background: var(--theme-color); }
|
|
870
914
|
|
|
871
915
|
/* Checkout stub */
|
|
872
916
|
.checkout-stub {
|
|
@@ -893,8 +937,16 @@ function buildPreviewHtml(schema, port) {
|
|
|
893
937
|
<span class="stub-tags">
|
|
894
938
|
<span class="stub-tag">Checkout: stubbed</span>
|
|
895
939
|
<span class="stub-tag">Analytics: off</span>
|
|
940
|
+
<span class="stub-tag clickable" id="pages-btn">Pages</span>
|
|
896
941
|
</span>
|
|
897
942
|
</div>
|
|
943
|
+
<div class="pages-overlay" id="pages-overlay">
|
|
944
|
+
<button class="close-btn" id="pages-close">×</button>
|
|
945
|
+
<div class="mindmap-container" id="mindmap-container"></div>
|
|
946
|
+
</div>
|
|
947
|
+
<div class="inspector-highlight" id="inspector-highlight" style="display:none"></div>
|
|
948
|
+
<div class="inspector-tooltip" id="inspector-tooltip" style="display:none"></div>
|
|
949
|
+
<div class="inspector-active-banner" id="inspector-banner" style="display:none">Inspector active — hover elements, click to copy</div>
|
|
898
950
|
<div id="catalog-root"></div>
|
|
899
951
|
|
|
900
952
|
<script id="__catalog_data" type="application/json">${schemaJson}</script>
|
|
@@ -1384,6 +1436,12 @@ function buildPreviewHtml(schema, port) {
|
|
|
1384
1436
|
const [formState, setFormState] = React.useState({});
|
|
1385
1437
|
const [history, setHistory] = React.useState([]);
|
|
1386
1438
|
|
|
1439
|
+
// Expose navigation for mindmap
|
|
1440
|
+
React.useEffect(() => {
|
|
1441
|
+
window.__devNavigateTo = (id) => { setCurrentPageId(id); setHistory([]); window.scrollTo({ top: 0, behavior: 'smooth' }); };
|
|
1442
|
+
window.__devSetCurrentPage && window.__devSetCurrentPage(currentPageId);
|
|
1443
|
+
}, [currentPageId]);
|
|
1444
|
+
|
|
1387
1445
|
const page = currentPageId ? pages[currentPageId] : null;
|
|
1388
1446
|
const isCover = page?.layout === 'cover';
|
|
1389
1447
|
const isLastPage = (() => {
|
|
@@ -1424,7 +1482,7 @@ function buildPreviewHtml(schema, port) {
|
|
|
1424
1482
|
|
|
1425
1483
|
// Cover page layout
|
|
1426
1484
|
if (isCover) {
|
|
1427
|
-
return h('div',
|
|
1485
|
+
return h('div', { 'data-page-id': currentPageId },
|
|
1428
1486
|
h('div', {
|
|
1429
1487
|
className: 'cf-page cf-noise min-h-screen flex items-center justify-center relative overflow-hidden',
|
|
1430
1488
|
style: {
|
|
@@ -1435,7 +1493,9 @@ function buildPreviewHtml(schema, port) {
|
|
|
1435
1493
|
h('div', { className: 'cf-cover-overlay absolute inset-0' }),
|
|
1436
1494
|
h('div', { className: 'cf-cover-content relative max-w-2xl mx-auto px-6 py-20 text-center text-white' },
|
|
1437
1495
|
h('div', { className: 'page-enter-active space-y-7 flex flex-col items-stretch text-center' },
|
|
1438
|
-
...components.map((comp, i) => h(
|
|
1496
|
+
...components.map((comp, i) => h('div', { key: comp.id || i, 'data-component-id': comp.id, 'data-component-type': comp.type },
|
|
1497
|
+
h(RenderComponent, { comp, isCover: true, formState, onFieldChange })
|
|
1498
|
+
)),
|
|
1439
1499
|
// CTA button
|
|
1440
1500
|
h('div', { className: 'mt-8' },
|
|
1441
1501
|
h('button', {
|
|
@@ -1446,9 +1506,7 @@ function buildPreviewHtml(schema, port) {
|
|
|
1446
1506
|
)
|
|
1447
1507
|
)
|
|
1448
1508
|
)
|
|
1449
|
-
)
|
|
1450
|
-
// Page nav
|
|
1451
|
-
h(PageNav, { pageKeys, pages, currentPageId, onSelect: (id) => { setCurrentPageId(id); setHistory([]); } })
|
|
1509
|
+
)
|
|
1452
1510
|
);
|
|
1453
1511
|
}
|
|
1454
1512
|
|
|
@@ -1457,7 +1515,7 @@ function buildPreviewHtml(schema, port) {
|
|
|
1457
1515
|
const progressSteps = catalog.settings?.progress_steps;
|
|
1458
1516
|
const topBarEnabled = topBar?.enabled !== false && catalog.settings?.top_bar;
|
|
1459
1517
|
|
|
1460
|
-
return h('div',
|
|
1518
|
+
return h('div', { 'data-page-id': currentPageId },
|
|
1461
1519
|
h('div', {
|
|
1462
1520
|
className: 'cf-page min-h-screen',
|
|
1463
1521
|
style: { background: 'linear-gradient(180deg, #f8f9fc 0%, #f0f2f7 100%)' },
|
|
@@ -1482,7 +1540,9 @@ function buildPreviewHtml(schema, port) {
|
|
|
1482
1540
|
h('div', { className: 'max-w-2xl mx-auto px-6 pb-8', style: { paddingTop: topBarEnabled ? '100px' : '60px' } },
|
|
1483
1541
|
page.description ? h('p', { className: 'text-sm text-gray-400 mb-8 text-center font-medium tracking-wide', style: { fontFamily: 'var(--font-display)' } }, page.description) : null,
|
|
1484
1542
|
h('div', { className: 'page-enter-active space-y-5' },
|
|
1485
|
-
...components.map((comp, i) => h(
|
|
1543
|
+
...components.map((comp, i) => h('div', { key: comp.id || i, 'data-component-id': comp.id, 'data-component-type': comp.type },
|
|
1544
|
+
h(RenderComponent, { comp, isCover: false, formState, onFieldChange })
|
|
1545
|
+
)),
|
|
1486
1546
|
// Navigation button
|
|
1487
1547
|
!page.hide_navigation ? h('div', { className: 'mt-8' },
|
|
1488
1548
|
h('button', {
|
|
@@ -1500,9 +1560,7 @@ function buildPreviewHtml(schema, port) {
|
|
|
1500
1560
|
),
|
|
1501
1561
|
h('div', { className: 'mt-10 text-center text-[11px] text-gray-300 font-medium tracking-wide', style: { fontFamily: 'var(--font-display)' } }, 'Powered by Catalog Kit'),
|
|
1502
1562
|
)
|
|
1503
|
-
)
|
|
1504
|
-
// Page nav
|
|
1505
|
-
h(PageNav, { pageKeys, pages, currentPageId, onSelect: (id) => { setCurrentPageId(id); setHistory([]); } })
|
|
1563
|
+
)
|
|
1506
1564
|
);
|
|
1507
1565
|
}
|
|
1508
1566
|
|
|
@@ -1532,16 +1590,6 @@ function buildPreviewHtml(schema, port) {
|
|
|
1532
1590
|
);
|
|
1533
1591
|
}
|
|
1534
1592
|
|
|
1535
|
-
function PageNav({ pageKeys, pages, currentPageId, onSelect }) {
|
|
1536
|
-
return h('div', { className: 'dev-page-nav' },
|
|
1537
|
-
...pageKeys.map(key => h('button', {
|
|
1538
|
-
key,
|
|
1539
|
-
className: key === currentPageId ? 'active' : '',
|
|
1540
|
-
onClick: () => onSelect(key),
|
|
1541
|
-
}, pages[key].title || key))
|
|
1542
|
-
);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
1593
|
// --- Auto-reload via SSE ---
|
|
1546
1594
|
const evtSource = new EventSource('/__dev_sse');
|
|
1547
1595
|
evtSource.onmessage = (e) => {
|
|
@@ -1553,6 +1601,224 @@ function buildPreviewHtml(schema, port) {
|
|
|
1553
1601
|
// --- Mount ---
|
|
1554
1602
|
const root = ReactDOM.createRoot(document.getElementById('catalog-root'));
|
|
1555
1603
|
root.render(h(CatalogPreview, { catalog: schema }));
|
|
1604
|
+
|
|
1605
|
+
// --- Pages mindmap ---
|
|
1606
|
+
(function initPagesMindmap() {
|
|
1607
|
+
const btn = document.getElementById('pages-btn');
|
|
1608
|
+
const overlay = document.getElementById('pages-overlay');
|
|
1609
|
+
const closeBtn = document.getElementById('pages-close');
|
|
1610
|
+
const container = document.getElementById('mindmap-container');
|
|
1611
|
+
let currentPageRef = { id: schema.routing?.entry || Object.keys(schema.pages || {})[0] };
|
|
1612
|
+
|
|
1613
|
+
// Expose setter for CatalogPreview to update current page
|
|
1614
|
+
window.__devSetCurrentPage = (id) => { currentPageRef.id = id; };
|
|
1615
|
+
|
|
1616
|
+
btn.addEventListener('click', () => {
|
|
1617
|
+
overlay.classList.add('open');
|
|
1618
|
+
renderMindmap();
|
|
1619
|
+
});
|
|
1620
|
+
closeBtn.addEventListener('click', () => overlay.classList.remove('open'));
|
|
1621
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.classList.remove('open'); });
|
|
1622
|
+
|
|
1623
|
+
function renderMindmap() {
|
|
1624
|
+
const pages = schema.pages || {};
|
|
1625
|
+
const routing = schema.routing || {};
|
|
1626
|
+
const edges = routing.edges || [];
|
|
1627
|
+
const entry = routing.entry || Object.keys(pages)[0];
|
|
1628
|
+
const pageIds = Object.keys(pages);
|
|
1629
|
+
|
|
1630
|
+
// Build adjacency for layout (BFS layers)
|
|
1631
|
+
const adj = {};
|
|
1632
|
+
pageIds.forEach(id => { adj[id] = []; });
|
|
1633
|
+
edges.forEach(e => { if (adj[e.from]) adj[e.from].push(e.to); });
|
|
1634
|
+
|
|
1635
|
+
// BFS to assign layers
|
|
1636
|
+
const layers = {};
|
|
1637
|
+
const visited = new Set();
|
|
1638
|
+
const queue = [entry];
|
|
1639
|
+
visited.add(entry);
|
|
1640
|
+
layers[entry] = 0;
|
|
1641
|
+
while (queue.length > 0) {
|
|
1642
|
+
const id = queue.shift();
|
|
1643
|
+
for (const next of (adj[id] || [])) {
|
|
1644
|
+
if (!visited.has(next)) {
|
|
1645
|
+
visited.add(next);
|
|
1646
|
+
layers[next] = (layers[id] || 0) + 1;
|
|
1647
|
+
queue.push(next);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
// Assign orphans
|
|
1652
|
+
pageIds.forEach(id => { if (layers[id] === undefined) layers[id] = 999; });
|
|
1653
|
+
|
|
1654
|
+
// Group by layer
|
|
1655
|
+
const layerGroups = {};
|
|
1656
|
+
pageIds.forEach(id => {
|
|
1657
|
+
const l = layers[id];
|
|
1658
|
+
if (!layerGroups[l]) layerGroups[l] = [];
|
|
1659
|
+
layerGroups[l].push(id);
|
|
1660
|
+
});
|
|
1661
|
+
const sortedLayers = Object.keys(layerGroups).map(Number).sort((a, b) => a - b);
|
|
1662
|
+
|
|
1663
|
+
// Render nodes in rows
|
|
1664
|
+
let html = '<div style="display:flex;flex-direction:column;align-items:center;gap:32px;">';
|
|
1665
|
+
const nodePositions = {};
|
|
1666
|
+
let rowIdx = 0;
|
|
1667
|
+
for (const layer of sortedLayers) {
|
|
1668
|
+
const ids = layerGroups[layer];
|
|
1669
|
+
html += '<div style="display:flex;gap:20px;justify-content:center;flex-wrap:wrap;">';
|
|
1670
|
+
ids.forEach(id => {
|
|
1671
|
+
const page = pages[id];
|
|
1672
|
+
const isEntry = id === entry;
|
|
1673
|
+
const isCurrent = id === currentPageRef.id;
|
|
1674
|
+
const compCount = (page.components || []).length;
|
|
1675
|
+
const cls = 'mindmap-node' + (isEntry ? ' entry' : '') + (isCurrent ? ' current' : '');
|
|
1676
|
+
html += '<div class="' + cls + '" data-node-id="' + id + '">';
|
|
1677
|
+
if (isEntry) html += '<span class="node-badge">entry</span>';
|
|
1678
|
+
html += '<div class="node-title">' + (page.title || id) + '</div>';
|
|
1679
|
+
html += '<div class="node-id">' + id + '</div>';
|
|
1680
|
+
html += '<div class="node-components">' + compCount + ' component' + (compCount !== 1 ? 's' : '') + '</div>';
|
|
1681
|
+
html += '</div>';
|
|
1682
|
+
});
|
|
1683
|
+
html += '</div>';
|
|
1684
|
+
rowIdx++;
|
|
1685
|
+
}
|
|
1686
|
+
html += '</div>';
|
|
1687
|
+
|
|
1688
|
+
container.innerHTML = html;
|
|
1689
|
+
|
|
1690
|
+
// Draw SVG edges after layout
|
|
1691
|
+
requestAnimationFrame(() => {
|
|
1692
|
+
const containerRect = container.getBoundingClientRect();
|
|
1693
|
+
const nodeEls = container.querySelectorAll('[data-node-id]');
|
|
1694
|
+
const nodeRects = {};
|
|
1695
|
+
nodeEls.forEach(el => { nodeRects[el.dataset.nodeId] = el.getBoundingClientRect(); });
|
|
1696
|
+
|
|
1697
|
+
let svgContent = '';
|
|
1698
|
+
edges.forEach(edge => {
|
|
1699
|
+
const fromRect = nodeRects[edge.from];
|
|
1700
|
+
const toRect = nodeRects[edge.to];
|
|
1701
|
+
if (!fromRect || !toRect) return;
|
|
1702
|
+
const x1 = fromRect.left + fromRect.width / 2 - containerRect.left;
|
|
1703
|
+
const y1 = fromRect.top + fromRect.height - containerRect.top;
|
|
1704
|
+
const x2 = toRect.left + toRect.width / 2 - containerRect.left;
|
|
1705
|
+
const y2 = toRect.top - containerRect.top;
|
|
1706
|
+
const midY = (y1 + y2) / 2;
|
|
1707
|
+
const hasConditions = edge.conditions && edge.conditions.length > 0;
|
|
1708
|
+
const color = hasConditions ? '#fbbf24' : 'rgba(255,255,255,0.25)';
|
|
1709
|
+
svgContent += '<path d="M' + x1 + ',' + y1 + ' C' + x1 + ',' + midY + ' ' + x2 + ',' + midY + ' ' + x2 + ',' + y2 + '" fill="none" stroke="' + color + '" stroke-width="2" stroke-dasharray="' + (hasConditions ? '6,4' : 'none') + '"/>';
|
|
1710
|
+
// Arrow
|
|
1711
|
+
svgContent += '<polygon points="' + (x2-4) + ',' + (y2-6) + ' ' + x2 + ',' + y2 + ' ' + (x2+4) + ',' + (y2-6) + '" fill="' + color + '"/>';
|
|
1712
|
+
// Condition label
|
|
1713
|
+
if (hasConditions) {
|
|
1714
|
+
const lx = (x1 + x2) / 2;
|
|
1715
|
+
const ly = midY - 8;
|
|
1716
|
+
const label = edge.conditions.map(c => c.field + ' ' + c.operator + ' ' + c.value).join(', ');
|
|
1717
|
+
svgContent += '<text x="' + lx + '" y="' + ly + '" text-anchor="middle" fill="#fbbf24" font-size="9" font-family="monospace">' + label + '</text>';
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1721
|
+
const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1722
|
+
svgEl.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;overflow:visible;';
|
|
1723
|
+
svgEl.innerHTML = svgContent;
|
|
1724
|
+
// Remove old svg
|
|
1725
|
+
const oldSvg = container.querySelector('svg');
|
|
1726
|
+
if (oldSvg) oldSvg.remove();
|
|
1727
|
+
container.insertBefore(svgEl, container.firstChild);
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
// Click nodes to navigate
|
|
1731
|
+
container.querySelectorAll('[data-node-id]').forEach(el => {
|
|
1732
|
+
el.addEventListener('click', () => {
|
|
1733
|
+
const id = el.dataset.nodeId;
|
|
1734
|
+
window.__devNavigateTo && window.__devNavigateTo(id);
|
|
1735
|
+
overlay.classList.remove('open');
|
|
1736
|
+
});
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
})();
|
|
1740
|
+
|
|
1741
|
+
// --- Element Inspector (Shift+Alt) ---
|
|
1742
|
+
(function initInspector() {
|
|
1743
|
+
const highlight = document.getElementById('inspector-highlight');
|
|
1744
|
+
const tooltip = document.getElementById('inspector-tooltip');
|
|
1745
|
+
const banner = document.getElementById('inspector-banner');
|
|
1746
|
+
let active = false;
|
|
1747
|
+
|
|
1748
|
+
document.addEventListener('keydown', (e) => {
|
|
1749
|
+
if (e.shiftKey && e.altKey && !active) {
|
|
1750
|
+
active = true;
|
|
1751
|
+
banner.style.display = 'block';
|
|
1752
|
+
document.body.style.cursor = 'crosshair';
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
document.addEventListener('keyup', (e) => {
|
|
1756
|
+
if (!e.shiftKey || !e.altKey) {
|
|
1757
|
+
active = false;
|
|
1758
|
+
banner.style.display = 'none';
|
|
1759
|
+
highlight.style.display = 'none';
|
|
1760
|
+
tooltip.style.display = 'none';
|
|
1761
|
+
document.body.style.cursor = '';
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
window.addEventListener('blur', () => {
|
|
1765
|
+
active = false;
|
|
1766
|
+
banner.style.display = 'none';
|
|
1767
|
+
highlight.style.display = 'none';
|
|
1768
|
+
tooltip.style.display = 'none';
|
|
1769
|
+
document.body.style.cursor = '';
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
document.addEventListener('mousemove', (e) => {
|
|
1773
|
+
if (!active) return;
|
|
1774
|
+
let el = e.target;
|
|
1775
|
+
// Walk up to find data-component-id
|
|
1776
|
+
while (el && !el.dataset?.componentId) el = el.parentElement;
|
|
1777
|
+
if (!el) {
|
|
1778
|
+
highlight.style.display = 'none';
|
|
1779
|
+
tooltip.style.display = 'none';
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
const compId = el.dataset.componentId;
|
|
1783
|
+
const compType = el.dataset.componentType || 'unknown';
|
|
1784
|
+
let pageEl = el;
|
|
1785
|
+
while (pageEl && !pageEl.dataset?.pageId) pageEl = pageEl.parentElement;
|
|
1786
|
+
const pageId = pageEl?.dataset?.pageId || 'unknown';
|
|
1787
|
+
const rect = el.getBoundingClientRect();
|
|
1788
|
+
highlight.style.display = 'block';
|
|
1789
|
+
highlight.style.top = (rect.top - 2) + 'px';
|
|
1790
|
+
highlight.style.left = (rect.left - 2) + 'px';
|
|
1791
|
+
highlight.style.width = (rect.width + 4) + 'px';
|
|
1792
|
+
highlight.style.height = (rect.height + 4) + 'px';
|
|
1793
|
+
const ref = pageId + '/' + compId;
|
|
1794
|
+
tooltip.style.display = 'block';
|
|
1795
|
+
tooltip.style.top = Math.max(8, rect.top - 44) + 'px';
|
|
1796
|
+
tooltip.style.left = Math.max(8, Math.min(rect.left, window.innerWidth - 340)) + 'px';
|
|
1797
|
+
tooltip.innerHTML = '<span class="ref">' + ref + '</span><span class="type">(' + compType + ')</span><div class="hint">click to copy</div>';
|
|
1798
|
+
tooltip.dataset.ref = ref;
|
|
1799
|
+
tooltip.dataset.compId = compId;
|
|
1800
|
+
tooltip.dataset.compType = compType;
|
|
1801
|
+
tooltip.dataset.pageId = pageId;
|
|
1802
|
+
});
|
|
1803
|
+
|
|
1804
|
+
document.addEventListener('click', (e) => {
|
|
1805
|
+
if (!active || tooltip.style.display === 'none') return;
|
|
1806
|
+
e.stopPropagation();
|
|
1807
|
+
e.preventDefault();
|
|
1808
|
+
const data = {
|
|
1809
|
+
ref: tooltip.dataset.ref,
|
|
1810
|
+
page_id: tooltip.dataset.pageId,
|
|
1811
|
+
component_id: tooltip.dataset.compId,
|
|
1812
|
+
component_type: tooltip.dataset.compType,
|
|
1813
|
+
schema_path: 'schema.pages.' + tooltip.dataset.pageId + '.components[id="' + tooltip.dataset.compId + '"]',
|
|
1814
|
+
catalog_slug: schema.slug || '',
|
|
1815
|
+
};
|
|
1816
|
+
navigator.clipboard.writeText(JSON.stringify(data, null, 2)).then(() => {
|
|
1817
|
+
tooltip.innerHTML = '<span style="color:#86efac;font-weight:700;">Copied!</span>';
|
|
1818
|
+
setTimeout(() => { tooltip.style.display = 'none'; }, 1200);
|
|
1819
|
+
});
|
|
1820
|
+
}, true);
|
|
1821
|
+
})();
|
|
1556
1822
|
</script>
|
|
1557
1823
|
</body>
|
|
1558
1824
|
</html>`;
|