Duda Collections Are Powerful, But Why Don't We Make Them Look Better?
5 MINUITE READ
Right, cards on the table. I was late to the party with Duda collections. Really late. And you know why? Because I'm a bit of a design snob. There, I said it.
Don't get me wrong – I've seen some decent collection implementations. But most of the examples I came across were... functional. And functional is great! But I've always believed that functional and beautiful aren't mutually exclusive. We can have both, yeah?
Collections are incredibly powerful – they pull dynamic data, update automatically, and save hours of manual work. But for the longest time, I struggled to make them match the design standards I wanted for my sites. Maybe you've been there too?
The Creative AI Revolution (Yes, I Said Creative)
When I started Agency Genius, the mission was simple: get more people using AI in their creative workflows. And yes, I said creative. Because AI is creative when the person behind the prompt gives it the right direction. But that's a discussion for another day.
What I want to share today is a workflow that's completely changed how I approach Duda collections. It's the perfect marriage of Duda's powerful data handling and AI's ability to create stunning designs.
The Free Tool That Changes Everything
I'm making a tool available to everyone – publicly, ungated, no login required. Use it to your heart's content. It's called the JS API Explorer, and when you combine it with Claude AI, something special happens.
Let me walk you through creating a "Meet the Team" component that'll make you proud to show it off.
Step 1: Plan Your Component
First, map out what you want. For our team component, let's go with:
- Person's name
- Their position
- A professional headshot
- A short bio about them
Pretty standard stuff, right? But here's where we can elevate it beyond the usual grid layout.
Step 2: Create Your Collection in Duda
Head into Duda and create a new collection. Call it something sensible like "Team Members" (we've all been guilty of "test123" at some point, haven't we?).
Add some dummy data:
- Name: Sarah Chen
- Position: Creative Director
- Image: Upload a headshot
- Bio : "Sarah brings 15 years of experience in transforming brands..."
Add a few more team members, then publish the collection . This bit's crucial – if you don't publish it, the magic won't happen.
Step 3: The JS API Explorer Magic
Now here's where it gets interesting. If you're a Pro member of AgencyGenius, you can request this as a Duda widget and just drag-and-drop it in. But if you're not (yet), you'll need to add the code directly... scroll down to find:
- If you have the Duda Widget drag in the JS API Explorer widget

Alternatively, if you're not yet a Pro member:
- Copy the 'Combined' tab
- Paste it into an HTML widget on your Duda site
<!-- Enhanced Duda JS API Explorer Widget This is a self-contained HTML widget that provides a comprehensive interface for exploring the full Duda JS API including Collections, Store API, Dynamic Pages, Content Library and core API methods. Created: May 2025 --> <div id="aog-enhanced-js-explorer-widget"> <!-- Widget Button --> <button id="aog-js-explorer-button" class="aog-js-explorer-button" type="button"> <span class="aog-js-explorer-icon">🔍</span> <span class="aog-js-explorer-text">JS API Explorer</span> </button> <!-- Widget Panel --> <div id="aog-js-explorer-panel" class="aog-js-explorer-panel"> <div class="aog-js-explorer-header"> <div class="aog-js-logo-container"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/Group+206.svg" alt="Agents of Genius" class="aog-js-logo"> </div> <h5 class="aog-js-title">JS API Explorer</h5> <div class="aog-js-explorer-controls"> <button id="aog-js-explorer-close" class="aog-js-explorer-close" type="button">×</button> </div> </div> <div class="aog-js-explorer-content"> <div id="aog-js-explorer-loading" class="aog-js-explorer-loading"> <div class="aog-js-spinner"></div> <div>Initializing API...</div> </div> <div id="aog-js-explorer-error" class="aog-js-explorer-error"> <div class="aog-js-error-icon">⚠️</div> <div> <strong>Error retrieving data</strong> <p id="aog-js-error-message">Could not load API data. Please try again.</p> </div> </div> <div id="aog-js-explorer-data"> <!-- Tabs Navigation --> <div class="aog-js-explorer-tabs"> <button class="aog-js-tab-button active" data-tab="api-discovery" type="button">API Discovery</button> <button class="aog-js-tab-button" data-tab="collections" type="button">Collections</button> <button class="aog-js-tab-button" data-tab="content-library" type="button">Content Library</button> <button class="aog-js-tab-button" data-tab="store" type="button">Store</button> <button class="aog-js-tab-button" data-tab="dynamic-pages" type="button">Dynamic Pages</button> <button class="aog-js-tab-button" data-tab="members" type="button">Members</button> <button class="aog-js-tab-button" data-tab="sitemap" type="button">Sitemap</button> <button class="aog-js-tab-button" data-tab="core-methods" type="button">Core Methods</button> <button class="aog-js-tab-button" data-tab="raw-json" type="button">Raw JSON</button> <button class="aog-js-tab-button" data-tab="api-settings" type="button">Settings</button> </div> <!-- Tab Content --> <div class="aog-js-explorer-tab-content"> <!-- API Discovery Tab --> <div id="api-discovery-tab" class="aog-js-tab-pane active"> <h5 class="aog-js-section-title">API Discovery</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Scan this Duda site to discover available API features and data sources.</p> <p>This will check for Collections, Content Library data, Store information, Dynamic Pages, and Members API.</p> </div> <button id="aog-js-discover-all" class="aog-js-primary-button"> <span class="aog-js-button-icon">🔍</span> Discover Available APIs </button> <div id="aog-js-discovery-results" class="aog-js-discovery-results"> <div class="aog-js-api-section" data-section="core"> <h5 class="aog-js-api-section-title">Core API</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="collections"> <h5 class="aog-js-api-section-title">Collections</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="contentLibrary"> <h5 class="aog-js-api-section-title">Content Library</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="store"> <h5 class="aog-js-api-section-title">Store API</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="dynamicPages"> <h5 class="aog-js-api-section-title">Dynamic Pages</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="members"> <h5 class="aog-js-api-section-title">Members API</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="sitemap"> <h5 class="aog-js-api-section-title">Sitemap</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> </div> </div> </div> </div> <!-- Collections Tab --> <div id="collections-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Collection Explorer</h5> <div class="aog-js-input-container"> <div class="aog-js-collection-actions"> <button id="aog-js-find-all-btn" class="aog-js-primary-button aog-js-secondary-button"> <span class="aog-js-button-icon">🔍</span> Find All Collections </button> <div class="aog-js-specific-collection"> <label for="aog-js-collection-input" class="aog-js-label">Specific Collection Name/ID:</label> <div class="aog-js-input-group"> <input id="aog-js-collection-input" type="text" placeholder="Enter collection name or ID" class="aog-js-input"> <button id="aog-js-find-btn" class="aog-js-action-button">Find</button> </div> </div> </div> </div> <div id="aog-js-collections-status" class="aog-js-status"></div> <div id="aog-js-discovered-collections" class="aog-js-discovered-collections"> <div class="aog-js-collections-header"> <div class="aog-js-section-subtitle">Discovered Collections</div> <div class="aog-js-discovery-count">0 found</div> </div> <div id="aog-js-collections-list" class="aog-js-collections-list"> <!-- Collections will be populated here --> <div class="aog-js-empty-state">No collections discovered yet. Use "API Discovery" to scan the page.</div> </div> </div> <div class="aog-js-filter-section"> <div class="aog-js-section-subtitle">Filtering & Sorting (Optional)</div> <div class="aog-js-filter-inputs"> <div class="aog-js-input-row"> <label for="aog-js-filter-field" class="aog-js-label">Field:</label> <input id="aog-js-filter-field" type="text" placeholder="Field name" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-filter-operator" class="aog-js-label">Operator:</label> <select id="aog-js-filter-operator" class="aog-js-input"> <option value="EQ">Equal to (EQ)</option> <option value="NE">Not equal to (NE)</option> <option value="GT">Greater than (GT)</option> <option value="GTE">Greater than or equal (GTE)</option> <option value="LT">Less than (LT)</option> <option value="LTE">Less than or equal (LTE)</option> <option value="IN">In array (IN)</option> <option value="NIN">Not in array (NIN)</option> <option value="BTWN">Between (BTWN)</option> </select> </div> <div class="aog-js-input-row"> <label for="aog-js-filter-value" class="aog-js-label">Value:</label> <input id="aog-js-filter-value" type="text" placeholder="Value or comma-separated values for IN/BTWN" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-sort-field" class="aog-js-label">Sort by:</label> <input id="aog-js-sort-field" type="text" placeholder="Field name (optional)" class="aog-js-input"> <select id="aog-js-sort-direction" class="aog-js-input aog-js-small-input"> <option value="asc">Ascending</option> <option value="desc">Descending</option> </select> </div> <div class="aog-js-input-row"> <label for="aog-js-page-size" class="aog-js-label">Page size:</label> <input id="aog-js-page-size" type="number" value="50" min="1" max="100" class="aog-js-input aog-js-small-input"> <label for="aog-js-page-number" class="aog-js-label aog-js-inline-label">Page:</label> <input id="aog-js-page-number" type="number" value="0" min="0" class="aog-js-input aog-js-small-input"> </div> </div> </div> </div> <!-- Content Library Tab --> <div id="content-library-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Content Library Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Explore site's business info, text and images from the Content Library API.</p> </div> <button id="aog-js-load-content-library" class="aog-js-primary-button"> <span class="aog-js-button-icon">📚</span> Load Content Library </button> <div id="aog-js-content-lib-status" class="aog-js-status"></div> <div id="aog-js-content-sections" class="aog-js-content-sections"> <div class="aog-js-content-section-list"> <div class="aog-js-content-section" data-section="business_data"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Business Data</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="location_data"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Location Data</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="additional_locations"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Additional Locations</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="site_texts"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Site Texts</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="site_images"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Site Images</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> </div> </div> </div> </div> </div> <!-- Store Tab --> <div id="store-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Store API Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Explore the Store API to access product catalog and categories.</p> </div> <div class="aog-js-store-actions"> <button id="aog-js-load-catalog" class="aog-js-action-button aog-js-wide-button"> <span class="aog-js-button-icon">🛒</span> Load Product Catalog </button> <button id="aog-js-load-categories" class="aog-js-action-button aog-js-wide-button"> <span class="aog-js-button-icon">📁</span> Load Product Categories </button> </div> <div id="aog-js-store-status" class="aog-js-status"></div> <div class="aog-js-filter-section"> <div class="aog-js-section-subtitle">Filtering & Sorting (Optional)</div> <div class="aog-js-filter-inputs"> <div class="aog-js-input-row"> <label for="aog-js-store-filter-field" class="aog-js-label">Field:</label> <input id="aog-js-store-filter-field" type="text" placeholder="Field name" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-store-filter-operator" class="aog-js-label">Operator:</label> <select id="aog-js-store-filter-operator" class="aog-js-input"> <option value="EQ">Equal to (EQ)</option> <option value="NE">Not equal to (NE)</option> <option value="GT">Greater than (GT)</option> <option value="GTE">Greater than or equal (GTE)</option> <option value="LT">Less than (LT)</option> <option value="LTE">Less than or equal (LTE)</option> <option value="IN">In array (IN)</option> <option value="NIN">Not in array (NIN)</option> <option value="BTWN">Between (BTWN)</option> </select> </div> <div class="aog-js-input-row"> <label for="aog-js-store-filter-value" class="aog-js-label">Value:</label> <input id="aog-js-store-filter-value" type="text" placeholder="Value or comma-separated values for IN/BTWN" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-store-sort-field" class="aog-js-label">Sort by:</label> <input id="aog-js-store-sort-field" type="text" placeholder="Field name (optional)" class="aog-js-input"> <select id="aog-js-store-sort-direction" class="aog-js-input aog-js-small-input"> <option value="asc">Ascending</option> <option value="desc">Descending</option> </select> </div> </div> </div> </div> </div> </div> <!-- Dynamic Pages Tab --> <div id="dynamic-pages-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Dynamic Pages Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Check if the current page is a dynamic page and explore its data.</p> </div> <button id="aog-js-check-dynamic-page" class="aog-js-primary-button"> <span class="aog-js-button-icon">📄</span> Check Current Page </button> <div id="aog-js-dynamic-page-status" class="aog-js-status"></div> <div id="aog-js-dynamic-page-info" class="aog-js-dynamic-page-info"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Is Dynamic Page:</div> <div id="aog-js-is-dynamic" class="aog-js-info-value">Unknown</div> </div> </div> </div> </div> </div> <!-- Core Methods Tab --> <div id="core-methods-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Core API Methods</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Access core API methods to get site information, navigation items, and more.</p> </div> <div class="aog-js-method-group"> <h3 class="aog-js-method-group-title">Site Information</h3> <div class="aog-js-method-buttons"> <button class="aog-js-method-button" data-method="getSiteName">getSiteName</button> <button class="aog-js-method-button" data-method="getSiteExternalId">getSiteExternalId</button> <button class="aog-js-method-button" data-method="getSitePlanID">getSitePlanID</button> <button class="aog-js-method-button" data-method="getCurrentDeviceType">getCurrentDeviceType</button> <button class="aog-js-method-button" data-method="getCurrentEnvironment">getCurrentEnvironment</button> </div> </div> <div class="aog-js-method-group"> <h3 class="aog-js-method-group-title">Navigation</h3> <div class="aog-js-method-buttons"> <button class="aog-js-method-button" data-method="getNavItemsAsync">getNavItemsAsync</button> </div> </div> <div id="aog-js-method-result-container" class="aog-js-method-result-container"> <div class="aog-js-section-subtitle">Method Result</div> <pre id="aog-js-method-result" class="aog-js-method-result">Click a method button to see its result</pre> </div> </div> </div> </div> <!-- Raw JSON Tab --> <div id="raw-json-tab" class="aog-js-tab-pane"> <div class="aog-js-code-controls"> <button id="aog-js-copy-json" class="aog-js-control-button" type="button">Copy JSON</button> <button id="aog-js-format-json" class="aog-js-control-button" type="button">Format JSON</button> </div> <pre id="aog-js-raw-json" class="aog-js-json-display">No data available. Use the other tabs to fetch data first.</pre> </div> <!-- Members API Tab --> <div id="members-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Members API Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Check for logged-in members and access member data.</p> </div> <button id="aog-js-check-member" class="aog-js-primary-button"> <span class="aog-js-button-icon">👤</span> Check Logged-in Member </button> <div id="aog-js-member-status" class="aog-js-status"></div> <div id="aog-js-member-info" class="aog-js-member-info"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Login Status:</div> <div id="aog-js-is-logged-in" class="aog-js-info-value">Unknown</div> </div> </div> </div> </div> </div> <!-- Sitemap Tab --> <div id="sitemap-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Sitemap Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Fetch and explore the site's sitemap.xml file.</p> </div> <button id="aog-js-fetch-sitemap" class="aog-js-primary-button"> <span class="aog-js-button-icon">🗺️</span> Fetch Sitemap </button> <div id="aog-js-sitemap-status" class="aog-js-status"></div> <div id="aog-js-sitemap-stats" class="aog-js-sitemap-stats" style="display: none;"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Total URLs:</div> <div id="aog-js-sitemap-count" class="aog-js-info-value">0</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Last Updated:</div> <div id="aog-js-sitemap-updated" class="aog-js-info-value">Unknown</div> </div> </div> <div id="aog-js-sitemap-container" class="aog-js-sitemap-container" style="display: none;"> <h5 class="aog-js-section-subtitle">Sitemap URLs</h5> <div id="aog-js-sitemap-urls" class="aog-js-sitemap-urls"></div> </div> </div> </div> </div> <!-- API Settings Tab --> <div id="api-settings-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">API Configuration</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Initialize the APIs to explore available data on this page.</p> </div> <button id="aog-js-init-all-btn" class="aog-js-primary-button"> <span class="aog-js-button-icon">⚡</span> Initialize All APIs </button> <div class="aog-js-api-info"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Collections API:</div> <div id="aog-js-collections-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Content Library:</div> <div id="aog-js-content-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Dynamic Pages API:</div> <div id="aog-js-dynamic-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Members API:</div> <div id="aog-js-members-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Core dmAPI:</div> <div id="aog-js-core-api-status" class="aog-js-info-value"> <span id="aog-js-core-api-available">Checking...</span> </div> </div> </div> <div class="aog-js-debug-section"> <div class="aog-js-section-subtitle">Debug Settings</div> <div class="aog-js-debug-toggle"> <label class="aog-js-switch"> <input type="checkbox" id="aog-js-debug-toggle"> <span class="aog-js-slider"></span> </label> <span class="aog-js-debug-label">Enable debug mode</span> </div> <p class="aog-js-debug-description"> When debug mode is enabled, detailed logs will be output to the browser console. This helps troubleshoot issues with API connections and data retrieval. </p> </div> </div> </div> </div> </div> </div> </div> </div> </div>
/* * Enhanced Duda JS API Explorer Widget - Styles * Uses Inter font family and adaptive colors */ /* Import Inter font */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); /* Define nav display styles */ .aog-js-nav-display { background-color: #f8f9fa; border-radius: 6px; padding: 12px; margin-bottom: 15px; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-nav-heading { font-weight: bold; margin-bottom: 10px; font-size: 15px; color: #1A1B4B; } .aog-js-nav-item { padding: 6px 0; border-bottom: 1px solid #eee; } .aog-js-nav-title { font-weight: 500; color: #1A1B4B; } .aog-js-nav-path { color: #666; font-size: 13px; } .aog-js-raw-link-container { text-align: right; padding-top: 10px; } .aog-js-show-raw { font-size: 13px; color: #6B46C1; text-decoration: none; } .aog-js-show-raw:hover { text-decoration: underline; } /* Widget Container */ #aog-enhanced-js-explorer-widget { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.5; color: #333; position: relative; z-index: 999999; } /* Button Styles */ .aog-js-explorer-button { position: fixed; bottom: 20px; left: 20px; background-color: #ad24a6; /* Agents of Genius CTA color */ color: white; border: none; border-radius: 50px; padding: 12px 20px; font-size: 14px; font-weight: 500; cursor: pointer; display: flex; align-items: center; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: all 0.3s ease; z-index: 999999; } .aog-js-explorer-button:hover { background-color: #851c83; /* Agents of Genius CTA hover color */ box-shadow: 0 4px 12px rgba(0,0,0,0.3); } .aog-js-explorer-icon { margin-right: 8px; font-weight: bold; font-size: 16px; } /* Panel Styles */ .aog-js-explorer-panel { position: fixed; bottom: 90px; left: 20px; width: 800px; height: 80vh; max-height: 800px; background-color: white; border-radius: 10px; /* Changed to 10px as requested */ box-shadow: 0 5px 25px rgba(0,0,0,0.3); display: none; flex-direction: column; z-index: 999998; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-explorer-header { background-color: #ad24a6; /* Agents of Genius CTA color */ padding: 15px 20px; border-bottom: 1px solid rgba(255,255,255,0.2); display: flex; justify-content: space-between; align-items: center; border-top-left-radius: 10px; border-top-right-radius: 10px; } .aog-js-logo-container { display: flex; align-items: center; height: 32px; } .aog-js-logo { height: 100%; max-width: 150px; } .aog-js-title { margin: 0; font-size: 16px; font-weight: 600; color: white !important; /* White text for better contrast */ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-explorer-controls { display: flex; align-items: center; } .aog-js-explorer-close { background: none; border: none; font-size: 24px; color: white; cursor: pointer; padding: 0 5px; line-height: 1; } .aog-js-explorer-content { padding: 0; overflow-y: auto; flex-grow: 1; position: relative; } /* Loading state */ .aog-js-explorer-loading { display: none; flex-direction: column; justify-content: center; align-items: center; height: 100%; padding: 30px; text-align: center; color: #666; } .aog-js-spinner { width: 40px; height: 40px; border: 3px solid #f3f3f3; border-top: 3px solid #ad24a6; /* Agents of Genius CTA color */ border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 15px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Error state */ .aog-js-explorer-error { display: none; padding: 30px; text-align: center; color: #e74c3c; flex-direction: column; align-items: center; justify-content: center; height: 100%; } .aog-js-error-icon { font-size: 40px; margin-bottom: 15px; } /* Tab navigation */ .aog-js-explorer-tabs { display: flex; border-bottom: 1px solid #E5E9F2; background-color: #F5F7FB; overflow-x: auto; white-space: nowrap; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-tab-button { padding: 12px 15px; background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 14px; font-weight: 500; color: #666; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-tab-button:hover { color: #ad24a6; /* Agents of Genius CTA color */ } .aog-js-tab-button.active { color: #ad24a6; /* Agents of Genius CTA color */ border-bottom-color: #ad24a6; /* Agents of Genius CTA color */ } /* Tab content */ .aog-js-explorer-tab-content { padding: 20px; } .aog-js-tab-pane { display: none; } .aog-js-tab-pane.active { display: block; } /* General data styles */ .aog-js-section-title { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 600; margin-bottom: 20px; color: #1A1B4B; } .aog-js-section-subtitle { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 600; color: #1A1B4B; margin-bottom: 12px; margin-top: 20px; } .aog-js-notice { padding: 10px; background-color: #f8f9fa; border-left: 4px solid #ad24a6; /* Agents of Genius CTA color */ margin-bottom: 15px; font-size: 13px; color: #666; } .aog-js-notice p { margin: 5px 0; } /* Input styles */ .aog-js-input-container { margin-bottom: 20px; } .aog-js-label { display: block; margin-bottom: 8px; font-weight: 500; color: #444; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-inline-label { display: inline-block; margin: 0 10px 0 15px; } .aog-js-input-group { display: flex; gap: 8px; } .aog-js-input { flex-grow: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-small-input { flex-grow: 0; width: 100px; } .aog-js-input:focus { outline: none; border-color: #ad24a6; } .aog-js-input-row { margin-bottom: 12px; display: flex; align-items: center; } .aog-js-filter-section { margin-top: 20px; border-top: 1px solid #eee; padding-top: 20px; } .aog-js-filter-inputs { background-color: #f8f9fa; padding: 15px; border-radius: 6px; } .aog-js-action-button { background-color: #333; color: white; border: none; border-radius: 6px; padding: 0 15px; font-weight: 500; cursor: pointer; transition: all 0.2s; height: 38px; display: flex; align-items: center; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-action-button:hover { background-color: #222; } .aog-js-wide-button { padding: 10px 20px; margin-right: 10px; margin-bottom: 10px; } .aog-js-store-actions { display: flex; flex-wrap: wrap; } /* Status indicator */ .aog-js-status { margin-bottom: 20px; padding: 12px 15px; border-radius: 6px; font-size: 14px; line-height: 1.4; display: none; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-status.success { background-color: #f0fff4; border-left: 3px solid #38a169; color: #2f855a; } .aog-js-status.error { background-color: #fff5f5; border-left: 3px solid #e53e3e; color: #c53030; } .aog-js-status.info { background-color: #f8f9fa; border-left: 3px solid #4299e1; color: #2b6cb0; } /* Discovered collections */ .aog-js-discovered-collections { margin-bottom: 20px; } .aog-js-collections-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .aog-js-discovery-count { font-size: 13px; color: #666; background-color: #f0f0f0; padding: 4px 8px; border-radius: 12px; } .aog-js-collections-list { background-color: #f8f9fa; border-radius: 6px; padding: 15px; max-height: 200px; overflow-y: auto; } .aog-js-collection-item { padding: 10px; background-color: white; border-radius: 4px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: all 0.2s; } .aog-js-collection-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .aog-js-collection-id { font-weight: 500; color: #333; } .aog-js-collection-load { color: #ad24a6; font-size: 13px; font-weight: 500; } .aog-js-empty-state { padding: 20px; text-align: center; color: #888; font-style: italic; } /* Card styles */ .aog-js-card { background-color: #ffffff; border-radius: 8px; margin-bottom: 20px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.05); border: 1px solid #E5E9F2; } .aog-js-card-body { padding: 20px; } /* Button styles */ .aog-js-primary-button { background-color: #ad24a6; color: white; border: none; border-radius: 6px; padding: 12px 20px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; width: 100%; margin-bottom: 15px; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-primary-button:hover { background-color: #851c83; } .aog-js-button-icon { margin-right: 8px; } /* API Info */ .aog-js-api-info { margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px; } .aog-js-info-item { display: flex; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 10px; } .aog-js-info-label { width: 120px; font-weight: 500; color: #444; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-info-value { flex-grow: 1; color: #666; } /* JSON display */ .aog-js-json-display { background-color: #f8f9fa; padding: 15px; border-radius: 6px; overflow: auto; font-family: Monaco, Menlo, Consolas, "Courier New", monospace; font-size: 13px; line-height: 1.5; color: #333; white-space: pre-wrap; max-height: 600px; } .aog-js-code-controls { margin-bottom: 10px; display: flex; gap: 8px; } .aog-js-control-button { background-color: #f1f3f5; border: 1px solid #dee2e6; border-radius: 4px; padding: 6px 12px; font-size: 13px; cursor: pointer; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-control-button:hover { background-color: #e9ecef; } /* Copied notification */ .aog-js-copied-notification { position: fixed; top: 20px; right: 20px; background-color: #ad24a6; color: white; padding: 12px 20px; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); opacity: 0; transform: translateY(-10px); transition: all 0.3s ease; pointer-events: none; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-copied-notification.show { opacity: 1; transform: translateY(0); } /* Core Methods */ .aog-js-method-group { margin-bottom: 20px; } .aog-js-method-group-title { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; margin-bottom: 10px; color: #1A1B4B; } .aog-js-method-buttons { display: flex; flex-wrap: wrap; gap: 8px; } .aog-js-method-button { background-color: #f1f3f5; border: 1px solid #dee2e6; border-radius: 4px; padding: 8px 12px; font-size: 13px; cursor: pointer; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-method-button:hover { background-color: #e9ecef; } .aog-js-method-result-container { margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px; } .aog-js-method-result { background-color: #f8f9fa; padding: 15px; border-radius: 6px; overflow: auto; font-family: Monaco, Menlo, Consolas, "Courier New", monospace; font-size: 13px; line-height: 1.5; color: #666; white-space: pre-wrap; max-height: 200px; } /* API Discovery Results */ .aog-js-discovery-results { margin-top: 20px; } .aog-js-api-section { border: 1px solid #e5e9f2; border-radius: 6px; margin-bottom: 15px; overflow: hidden; } .aog-js-api-section-title { margin: 0; font-size: 15px; font-weight: 500; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-api-section h5 { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; background-color: #f5f7fb; padding: 10px 15px; border-bottom: 1px solid #e5e9f2; display: flex; justify-content: space-between; align-items: center; margin: 0; color: #1A1B4B; } .aog-js-api-section-status { font-size: 13px; color: #666; font-weight: normal; } .aog-js-api-section-results { padding: 15px; max-height: 200px; overflow-y: auto; } /* Content Library */ .aog-js-content-sections { margin-top: 20px; } .aog-js-content-section { border: 1px solid #e5e9f2; border-radius: 6px; margin-bottom: 10px; overflow: hidden; } /* Collection actions */ .aog-js-collection-actions { display: flex; flex-direction: column; gap: 15px; width: 100%; } .aog-js-secondary-button { background-color: #6B46C1; margin-bottom: 5px; } .aog-js-secondary-button:hover { background-color: #553C9A; } .aog-js-specific-collection { width: 100%; } .aog-js-content-section-header { background-color: #f5f7fb; padding: 10px 15px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } .aog-js-content-section-title { font-weight: 500; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-content-section-body { padding: 0; max-height: 0; overflow: hidden; transition: all 0.3s ease; } .aog-js-content-section.expanded .aog-js-content-section-body { padding: 15px; max-height: 500px; overflow-y: auto; } .aog-js-content-section.expanded .aog-js-content-section-toggle { transform: rotate(90deg); } .aog-js-content-section-toggle { transition: transform 0.3s ease; } /* Debug Toggle */ .aog-js-debug-section { margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px; } .aog-js-debug-toggle { display: flex; align-items: center; } .aog-js-switch { position: relative; display: inline-block; width: 50px; height: 24px; margin-right: 10px; } .aog-js-switch input { opacity: 0; width: 0; height: 0; } .aog-js-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; } .aog-js-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .aog-js-slider { background-color: #ad24a6; } input:checked + .aog-js-slider:before { transform: translateX(26px); } .aog-js-debug-label { font-size: 14px; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-debug-description { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 13px; color: #666; line-height: 1.5; } /* Mobile responsiveness */ @media (max-width: 992px) { .aog-js-explorer-panel { width: calc(100% - 40px); height: 80vh; bottom: 80px; } .aog-js-explorer-tabs { overflow-x: auto; white-space: nowrap; } .aog-js-input-row { flex-direction: column; align-items: flex-start; } .aog-js-inline-label { margin: 8px 0; } .aog-js-store-actions { flex-direction: column; } .aog-js-wide-button { width: 100%; margin-right: 0; } } @media (max-width: 480px) { .aog-js-method-buttons { flex-direction: column; } .aog-js-method-button { width: 100%; } } /* Sitemap Styles */ .aog-js-sitemap-url-list { list-style: none; padding: 0; margin: 0; } .aog-js-sitemap-url-item { padding: 8px 0; border-bottom: 1px solid #eee; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-sitemap-url-item a { color: #4a5568; text-decoration: none; } .aog-js-sitemap-url-item a:hover { color: #ad24a6; text-decoration: underline; } .aog-js-sitemap-lastmod { font-size: 12px; color: #718096; margin-left: 8px; }
// Enhanced JS API Explorer Widget - Agents of Genius Branded JavaScript // IIFE to avoid polluting global scope (function() { // Debug flag - can be toggled via UI let DEBUG = false; // Debug logging function function debug(message, data) { if (DEBUG) { console.log(`[AOGJS Debug] ${message}`, data || ''); } } // Initialize immediately debug('Script loaded, initializing...'); document.addEventListener('DOMContentLoaded', initEnhancedJSAPIExplorer); // Main initialization function function initEnhancedJSAPIExplorer() { // API State let apiState = { collectionsAPI: null, contentLibraryAPI: null, dynamicPagesAPI: null, currentData: null, discoveredCollections: [], isDudaSite: false }; // DOM Elements const explorerButton = document.getElementById('aog-js-explorer-button'); const explorerPanel = document.getElementById('aog-js-explorer-panel'); const closeButton = document.getElementById('aog-js-explorer-close'); const loadingEl = document.getElementById('aog-js-explorer-loading'); const errorEl = document.getElementById('aog-js-explorer-error'); const errorMessage = document.getElementById('aog-js-error-message'); const dataEl = document.getElementById('aog-js-explorer-data'); // Settings Elements const initAllBtn = document.getElementById('aog-js-init-all-btn'); const collectionsApiStatusEl = document.getElementById('aog-js-collections-api-status'); const contentApiStatusEl = document.getElementById('aog-js-content-api-status'); const dynamicApiStatusEl = document.getElementById('aog-js-dynamic-api-status'); const coreApiAvailableEl = document.getElementById('aog-js-core-api-available'); const debugToggle = document.getElementById('aog-js-debug-toggle'); // Collections Tab Elements const findBtn = document.getElementById('aog-js-find-btn'); // Removed findAllBtn reference since we're removing that button const collectionInput = document.getElementById('aog-js-collection-input'); const collectionsStatusEl = document.getElementById('aog-js-collections-status'); const collectionsListEl = document.getElementById('aog-js-collections-list'); const discoveryCountEl = document.querySelector('.aog-js-discovery-count'); // Filter Elements const filterFieldEl = document.getElementById('aog-js-filter-field'); const filterOperatorEl = document.getElementById('aog-js-filter-operator'); const filterValueEl = document.getElementById('aog-js-filter-value'); const sortFieldEl = document.getElementById('aog-js-sort-field'); const sortDirectionEl = document.getElementById('aog-js-sort-direction'); const pageSizeEl = document.getElementById('aog-js-page-size'); const pageNumberEl = document.getElementById('aog-js-page-number'); // Store Tab Elements const loadCatalogBtn = document.getElementById('aog-js-load-catalog'); const loadCategoriesBtn = document.getElementById('aog-js-load-categories'); const storeStatusEl = document.getElementById('aog-js-store-status'); const storeFilterFieldEl = document.getElementById('aog-js-store-filter-field'); const storeFilterOperatorEl = document.getElementById('aog-js-store-filter-operator'); const storeFilterValueEl = document.getElementById('aog-js-store-filter-value'); const storeSortFieldEl = document.getElementById('aog-js-store-sort-field'); const storeSortDirectionEl = document.getElementById('aog-js-store-sort-direction'); // Content Library Tab Elements const loadContentLibraryBtn = document.getElementById('aog-js-load-content-library'); const contentLibStatusEl = document.getElementById('aog-js-content-lib-status'); const contentSectionElements = document.querySelectorAll('.aog-js-content-section'); // Dynamic Pages Tab Elements const checkDynamicPageBtn = document.getElementById('aog-js-check-dynamic-page'); const dynamicPageStatusEl = document.getElementById('aog-js-dynamic-page-status'); const isDynamicEl = document.getElementById('aog-js-is-dynamic'); // Core Methods Tab Elements const methodButtons = document.querySelectorAll('.aog-js-method-button'); const methodResultEl = document.getElementById('aog-js-method-result'); // API Discovery Tab Elements const discoverAllBtn = document.getElementById('aog-js-discover-all'); const apiSections = document.querySelectorAll('.aog-js-api-section'); // Raw JSON Tab Elements const rawJsonEl = document.getElementById('aog-js-raw-json'); const copyJsonBtn = document.getElementById('aog-js-copy-json'); const formatJsonBtn = document.getElementById('aog-js-format-json'); // Tab Elements const tabButtons = document.querySelectorAll('.aog-js-tab-button'); const tabPanes = document.querySelectorAll('.aog-js-tab-pane'); // UI State let isOpen = false; // Apply style updates function applyStyleUpdates() { // 1. Reduce size of section titles in API discovery and other tabs const sectionTitles = document.querySelectorAll('.aog-js-section-title'); sectionTitles.forEach(title => { title.style.fontSize = '16px'; title.style.fontWeight = '600'; }); // 2. Reduce size of section subtitles const sectionSubtitles = document.querySelectorAll('.aog-js-section-subtitle'); sectionSubtitles.forEach(subtitle => { subtitle.style.fontSize = '14px'; subtitle.style.fontWeight = '600'; }); // 3. Make all CTA buttons the same color (#ad24a6 - purple) const ctaButtons = document.querySelectorAll('.aog-js-primary-button'); ctaButtons.forEach(button => { button.style.backgroundColor = '#ad24a6'; button.style.color = 'white'; }); // 4. Remove the main JS API Explorer title from header const headerTitle = document.querySelector('.aog-js-explorer-header h2'); if (headerTitle) { headerTitle.style.display = 'none'; } // 5. Remove "Find All Collections" button if it exists const findAllBtn = document.getElementById('aog-js-find-all-btn'); if (findAllBtn) { findAllBtn.style.display = 'none'; } // 6. Update the collections tab description const collectionsDesc = document.querySelector('.aog-js-collections-description'); if (collectionsDesc) { collectionsDesc.textContent = 'Enter a collection name or ID to retrieve data. Use filters and sorting options below for more specific queries.'; } // 7. Update the API discovery tab description const apiDiscoveryDesc = document.querySelector('.aog-js-api-discovery-description'); if (apiDiscoveryDesc) { apiDiscoveryDesc.textContent = 'Discover available APIs on this site. To search for collections, go to the Collections tab and enter a collection name.'; } } // Check if running on a Duda site function checkDudaEnvironment() { const isDudaSite = typeof window.dmAPI !== 'undefined'; debug('Checking Duda environment', { isDudaSite }); apiState.isDudaSite = isDudaSite; if (isDudaSite) { coreApiAvailableEl.textContent = 'Available ✓'; coreApiAvailableEl.style.color = '#38a169'; } else { coreApiAvailableEl.textContent = 'Not detected'; coreApiAvailableEl.style.color = '#e53e3e'; // Show warning in API discovery tab showStatus('Warning: dmAPI not detected. Are you on a Duda site?', 'error', 'api-discovery-tab'); } return isDudaSite; } // Tab switching function switchTab(event) { const tabId = event.target.getAttribute('data-tab'); debug('Tab switch to:', tabId); // Update active tab button tabButtons.forEach(button => { button.classList.remove('active'); }); event.target.classList.add('active'); // Update active tab pane tabPanes.forEach(pane => { pane.classList.remove('active'); }); document.getElementById(`${tabId}-tab`).classList.add('active'); } // Toggle panel function togglePanel() { debug('Toggle panel called, current state:', isOpen); if (isOpen) { explorerPanel.style.display = 'none'; } else { explorerPanel.style.display = 'flex'; // Check if running on a Duda site checkDudaEnvironment(); // Apply style updates applyStyleUpdates(); } isOpen = !isOpen; } // Close panel function closePanel() { debug('Close panel called'); explorerPanel.style.display = 'none'; isOpen = false; } // Show status message in a specific tab function showStatus(message, type = 'info', tabId = null) { debug(`Status: ${message} (${type}) in tab: ${tabId || 'current'}`); let statusEl; // Determine which status element to use if (tabId === 'collections-tab') { statusEl = collectionsStatusEl; } else if (tabId === 'store-tab') { statusEl = storeStatusEl; } else if (tabId === 'content-library-tab') { statusEl = contentLibStatusEl; } else if (tabId === 'dynamic-pages-tab') { statusEl = dynamicPageStatusEl; } else if (tabId === 'api-discovery-tab') { // For discovery tab, show in all status elements const discoveryStatusEl = document.querySelector('#aog-js-discovery-results .aog-js-status'); if (discoveryStatusEl) { discoveryStatusEl.textContent = message; discoveryStatusEl.className = 'aog-js-status'; discoveryStatusEl.classList.add(type); discoveryStatusEl.style.display = 'block'; } return; } else { // Default to collections status statusEl = collectionsStatusEl; } statusEl.textContent = message; statusEl.className = 'aog-js-status'; // Reset classes statusEl.classList.add(type); statusEl.style.display = 'block'; // Auto-hide success messages after 5 seconds if (type === 'success') { setTimeout(() => { statusEl.style.display = 'none'; }, 5000); } } // Create collection item (for when collection is added to discovered collections) function createCollectionItem(id) { const item = document.createElement('div'); item.className = 'aog-js-collection-item'; const idEl = document.createElement('div'); idEl.className = 'aog-js-collection-id'; idEl.textContent = id; const loadEl = document.createElement('div'); loadEl.className = 'aog-js-collection-load'; loadEl.textContent = 'Load →'; item.appendChild(idEl); item.appendChild(loadEl); // Add click event to load this collection item.addEventListener('click', () => { collectionInput.value = id; findCollection(id); }); return item; } // Initialize all APIs async function initAllAPIs() { debug('Initializing all APIs...'); if (!apiState.isDudaSite) { showAllErrors('dmAPI not detected. Cannot initialize APIs.'); return; } // Show loading state dataEl.style.display = 'none'; loadingEl.style.display = 'flex'; try { // Initialize Collections API try { apiState.collectionsAPI = await window.dmAPI.loadCollectionsAPI(); collectionsApiStatusEl.textContent = 'Initialized ✓'; collectionsApiStatusEl.style.color = '#38a169'; } catch (error) { debug('Error initializing Collections API:', error); collectionsApiStatusEl.textContent = 'Failed: ' + error.message; collectionsApiStatusEl.style.color = '#e53e3e'; } // Initialize Content Library API try { apiState.contentLibraryAPI = await window.dmAPI.loadContentLibrary(); contentApiStatusEl.textContent = 'Initialized ✓'; contentApiStatusEl.style.color = '#38a169'; } catch (error) { debug('Error initializing Content Library API:', error); contentApiStatusEl.textContent = 'Failed: ' + error.message; contentApiStatusEl.style.color = '#e53e3e'; } // Initialize Dynamic Pages API try { apiState.dynamicPagesAPI = window.dmAPI.dynamicPageApi(); dynamicApiStatusEl.textContent = 'Initialized ✓'; dynamicApiStatusEl.style.color = '#38a169'; } catch (error) { debug('Error initializing Dynamic Pages API:', error); dynamicApiStatusEl.textContent = 'Failed: ' + error.message; dynamicApiStatusEl.style.color = '#e53e3e'; } // Check Members API const membersApiStatusEl = document.getElementById('aog-js-members-api-status'); try { if (typeof window.dmAPI.getLoggedInMember === 'function') { membersApiStatusEl.textContent = 'Available ✓'; membersApiStatusEl.style.color = '#38a169'; } else { membersApiStatusEl.textContent = 'Not Available'; membersApiStatusEl.style.color = '#dd6b20'; } } catch (error) { debug('Error checking Members API:', error); membersApiStatusEl.textContent = 'Failed: ' + error.message; membersApiStatusEl.style.color = '#e53e3e'; } // Show data section loadingEl.style.display = 'none'; dataEl.style.display = 'block'; showStatus('✅ APIs initialized successfully!', 'success', 'api-settings-tab'); } catch (error) { loadingEl.style.display = 'none'; dataEl.style.display = 'block'; showStatus(`❌ Error initializing APIs: ${error.message}`, 'error', 'api-settings-tab'); debug('Full initialization error:', error); } } // Show error in all status elements function showAllErrors(errorMsg) { showStatus(errorMsg, 'error', 'collections-tab'); showStatus(errorMsg, 'error', 'content-library-tab'); showStatus(errorMsg, 'error', 'store-tab'); showStatus(errorMsg, 'error', 'dynamic-pages-tab'); showStatus(errorMsg, 'error', 'api-discovery-tab'); showStatus(errorMsg, 'error', 'api-settings-tab'); } // Find collection with filters and also display in collections tab async function findCollection(collectionId) { if (!apiState.isDudaSite) { showStatus('Cannot find collection: dmAPI not detected', 'error', 'collections-tab'); return; } if (!apiState.collectionsAPI) { showStatus('Collections API not initialized. Click "Initialize All APIs" in Settings tab first.', 'error', 'collections-tab'); return; } if (!collectionId) { collectionId = collectionInput.value.trim(); } if (!collectionId) { showStatus('Please enter a collection name or ID', 'error', 'collections-tab'); return; } debug(`Finding collection: ${collectionId}`); showStatus(`Searching for collection: ${collectionId}...`, 'info', 'collections-tab'); try { // Build query with filters if provided let query = apiState.collectionsAPI.data(collectionId); // Add where clause if field is provided const filterField = filterFieldEl.value.trim(); const filterOperator = filterOperatorEl.value; const filterValue = filterValueEl.value.trim(); if (filterField && filterValue) { // Handle array operators (IN, NIN, BTWN) if (['IN', 'NIN', 'BTWN'].includes(filterOperator)) { const valueArray = filterValue.split(',').map(v => v.trim()); query = query.where(filterField, filterOperator, valueArray); } else { // Convert to number if it looks like one const value = !isNaN(filterValue) ? parseFloat(filterValue) : filterValue; query = query.where(filterField, filterOperator, value); } } // Add sort if field is provided const sortField = sortFieldEl.value.trim(); const sortDirection = sortDirectionEl.value; if (sortField) { query = query.orderBy(sortField, sortDirection); } // Add pagination const pageSize = parseInt(pageSizeEl.value) || 50; const pageNumber = parseInt(pageNumberEl.value) || 0; query = query.pageSize(pageSize).pageNumber(pageNumber); // Execute query const data = await query.get(); // Check if the response is actually an error if (isErrorResponse(data)) { showStatus(`❌ Error: ${data.message || 'Collection not found'}`, 'error', 'collections-tab'); showResults(data); return; } // Check if the response is valid and contains data if (data) { let itemCount = 0; let dataType = 'unknown'; // For array responses if (Array.isArray(data)) { itemCount = data.length; dataType = 'array'; if (data.length > 0) { showStatus(`✅ Successfully retrieved collection: ${collectionId} (${data.length} items)`, 'success', 'collections-tab'); } else { showStatus('⚠️ Collection found but contains no items.', 'info', 'collections-tab'); } } // For object responses like the one with values property else if (data.values && Array.isArray(data.values)) { itemCount = data.values.length; dataType = 'object with values array'; if (data.values.length > 0) { showStatus(`✅ Successfully retrieved collection: ${collectionId} (${data.values.length} items)`, 'success', 'collections-tab'); } else { showStatus('⚠️ Collection found but contains no items.', 'info', 'collections-tab'); } } // For other types of responses else if (typeof data === 'object' && Object.keys(data).length > 0) { itemCount = Object.keys(data).length; dataType = 'object'; showStatus(`✅ Successfully retrieved data for: ${collectionId}`, 'success', 'collections-tab'); } else { showStatus('⚠️ Response received but with unexpected format.', 'info', 'collections-tab'); } // Add the collection to discovered collections if not already there if (!apiState.discoveredCollections.includes(collectionId)) { apiState.discoveredCollections.push(collectionId); updateDiscoveredCollections(); } // Display in Raw JSON tab showResults(data); // Also display in collections tab displayCollectionData(collectionId, data, itemCount, dataType); } else { showStatus('⚠️ Received empty response.', 'error', 'collections-tab'); showResults({}); } } catch (error) { showStatus(`❌ Error finding collection: ${error.message}`, 'error', 'collections-tab'); showResults({ error: error.message }); // Try alternative methods if API call fails tryAlternativeMethods(collectionId); } } // Display collection data in the collections tab function displayCollectionData(collectionId, data, itemCount, dataType) { // Check if collection details section exists, create if not let collectionDetailsEl = document.getElementById('aog-js-collection-details'); if (!collectionDetailsEl) { collectionDetailsEl = document.createElement('div'); collectionDetailsEl.id = 'aog-js-collection-details'; collectionDetailsEl.className = 'aog-js-collection-details'; // Find where to insert it (after the discovered collections) const insertAfter = document.getElementById('aog-js-discovered-collections'); if (insertAfter && insertAfter.parentNode) { insertAfter.parentNode.insertBefore(collectionDetailsEl, insertAfter.nextSibling); } else { // Fallback - try to insert before the filter section const filterSection = document.querySelector('.aog-js-filter-section'); if (filterSection && filterSection.parentNode) { filterSection.parentNode.insertBefore(collectionDetailsEl, filterSection); } } } // Create the preview content collectionDetailsEl.innerHTML = ''; // Create the header const headerEl = document.createElement('div'); headerEl.className = 'aog-js-section-subtitle'; headerEl.textContent = 'Collection Preview'; collectionDetailsEl.appendChild(headerEl); // Create the details container const detailsContainer = document.createElement('div'); detailsContainer.className = 'aog-js-card'; const detailsBody = document.createElement('div'); detailsBody.className = 'aog-js-card-body'; // Create collection info const infoEl = document.createElement('div'); infoEl.className = 'aog-js-collection-info'; infoEl.innerHTML = ` <div class="aog-js-info-item"> <div class="aog-js-info-label">Collection Name:</div> <div class="aog-js-info-value">${collectionId}</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Items Count:</div> <div class="aog-js-info-value">${itemCount}</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Data Type:</div> <div class="aog-js-info-value">${dataType}</div> </div> `; detailsBody.appendChild(infoEl); // Create data preview const previewTitle = document.createElement('div'); previewTitle.className = 'aog-js-section-subtitle'; previewTitle.textContent = 'Data Preview'; previewTitle.style.marginTop = '20px'; detailsBody.appendChild(previewTitle); // Format the data for display let previewContent = ''; try { // Show a truncated version (first item if array, or first few properties if object) if (Array.isArray(data) && data.length > 0) { previewContent = JSON.stringify(data[0], null, 2); if (data.length > 1) { previewContent += `\n\n// ... and ${data.length - 1} more items`; } } else if (data.values && Array.isArray(data.values) && data.values.length > 0) { previewContent = JSON.stringify(data.values[0], null, 2); if (data.values.length > 1) { previewContent += `\n\n// ... and ${data.values.length - 1} more items`; } } else { // For objects, limit the preview const fullJson = JSON.stringify(data, null, 2); previewContent = fullJson.length > 1000 ? fullJson.substring(0, 1000) + '...' : fullJson; } } catch (error) { previewContent = 'Error formatting data preview: ' + error.message; } const previewEl = document.createElement('pre'); previewEl.className = 'aog-js-collection-preview'; previewEl.textContent = previewContent; detailsBody.appendChild(previewEl); // Add view in raw JSON link const viewRawLink = document.createElement('button'); viewRawLink.className = 'aog-js-primary-button'; viewRawLink.style.marginTop = '15px'; viewRawLink.style.backgroundColor = '#ad24a6'; // Consistent purple color viewRawLink.textContent = 'View Full Data in Raw JSON Tab'; viewRawLink.addEventListener('click', () => { // Switch to raw JSON tab switchToTab('raw-json'); }); detailsBody.appendChild(viewRawLink); detailsContainer.appendChild(detailsBody); collectionDetailsEl.appendChild(detailsContainer); // Make sure the details are visible collectionDetailsEl.style.display = 'block'; } // Update discovered collections UI function updateDiscoveredCollections() { if (!collectionsListEl) return; collectionsListEl.innerHTML = ''; if (apiState.discoveredCollections.length === 0) { const emptyState = document.createElement('div'); emptyState.className = 'aog-js-empty-state'; emptyState.textContent = 'No discovered collections yet. Search for a collection by name in the field above.'; collectionsListEl.appendChild(emptyState); return; } // Update count if element exists if (discoveryCountEl) { discoveryCountEl.textContent = `${apiState.discoveredCollections.length} found`; } // Add each collection to the list apiState.discoveredCollections.forEach(id => { collectionsListEl.appendChild(createCollectionItem(id)); }); } // Try alternative methods to find collection async function tryAlternativeMethods(identifier) { debug('Trying alternative methods for: ' + identifier); showStatus('Trying alternative lookup methods...', 'info', 'collections-tab'); let found = false; // Method 1: Try to access using external data source if available if (typeof window.dm !== 'undefined' && typeof window.dm.getExternalDataSource === 'function') { try { const externalDataId = isNaN(identifier) ? identifier : parseInt(identifier); const externalData = await window.dm.getExternalDataSource(externalDataId); if (externalData && externalData.items) { showStatus(`✅ Found collection using dm.getExternalDataSource`, 'success', 'collections-tab'); showResults(externalData.items); // Also display in collections tab displayCollectionData(identifier, externalData.items, externalData.items.length, 'external data array'); found = true; // Add to discovered collections if not already there if (!apiState.discoveredCollections.includes(identifier)) { apiState.discoveredCollections.push(identifier); updateDiscoveredCollections(); } } } catch (error) { debug('Error using getExternalDataSource:', error.message); } } // Method 2: Try to look for global collections variable if (!found && (window.collectionsData || window.collections)) { const collections = window.collectionsData || window.collections; if (collections && collections[identifier]) { showStatus(`✅ Found collection in global variables`, 'success', 'collections-tab'); showResults(collections[identifier]); // Also display in collections tab displayCollectionData(identifier, collections[identifier], Array.isArray(collections[identifier]) ? collections[identifier].length : 1, 'global collection'); found = true; // Add to discovered collections if not already there if (!apiState.discoveredCollections.includes(identifier)) { apiState.discoveredCollections.push(identifier); updateDiscoveredCollections(); } } } if (!found) { showStatus(`Could not find collection "${identifier}" using any method.`, 'error', 'collections-tab'); } } // Discover APIs - Modified to not search for collections async function discoverAPIs() { if (!apiState.isDudaSite) { updateApiSectionStatus('core', 'Error', 'dmAPI not detected'); updateApiSectionStatus('collections', 'Error', 'dmAPI not detected'); updateApiSectionStatus('contentLibrary', 'Error', 'dmAPI not detected'); updateApiSectionStatus('store', 'Error', 'dmAPI not detected'); updateApiSectionStatus('dynamicPages', 'Error', 'dmAPI not detected'); updateApiSectionStatus('members', 'Error', 'dmAPI not detected'); updateApiSectionStatus('sitemap', 'Error', 'dmAPI not detected'); return; } debug('Discovering APIs...'); showStatus('Scanning site for available APIs...', 'info', 'api-discovery-tab'); // Initialize APIs if needed if (!apiState.collectionsAPI || !apiState.contentLibraryAPI || !apiState.dynamicPagesAPI) { await initAllAPIs(); } // Check core API methods discoverCoreAPI(); // Check if collections API is available (but don't search for collections) checkCollectionsAPIAvailability(); // Check content library discoverContentLibrary(); // Check store API discoverStoreAPI(); // Check dynamic pages discoverDynamicPages(); // Check members API discoverMembersAPI(); // Check sitemap discoverSitemap(); } // Check if Collections API is available without searching for collections async function checkCollectionsAPIAvailability() { updateApiSectionStatus('collections', 'Checking...'); try { if (!apiState.collectionsAPI) { try { apiState.collectionsAPI = await window.dmAPI.loadCollectionsAPI(); } catch (error) { updateApiSectionStatus('collections', 'Not Available', error.message); return; } } // If we get here, the API is available updateApiSectionStatus('collections', 'Available', 'Use Collections tab to search for collections'); // Display guidance in the results section const resultsEl = document.querySelector('.aog-js-api-section[data-section="collections"] .aog-js-api-section-results'); if (resultsEl) { resultsEl.innerHTML = '<div class="aog-js-guidance">Collections API is available. Go to the Collections tab to search for specific collections by name.</div>'; } } catch (error) { updateApiSectionStatus('collections', 'Error', error.message); } } // Find store catalog or categories async function findStoreData(type) { if (!apiState.isDudaSite) { showStatus('Cannot access store data: dmAPI not detected', 'error', 'store-tab'); return; } if (!apiState.collectionsAPI) { showStatus('Collections API not initialized. Click "Initialize All APIs" in Settings tab first.', 'error', 'store-tab'); return; } const storeType = type === 'catalog' ? 'catalog_product' : 'catalog_category'; debug(`Finding store ${type}`); showStatus(`Searching for store ${type}...`, 'info', 'store-tab'); try { // Build query with filters if provided let query = apiState.collectionsAPI.storeData(storeType); // Add where clause if field is provided const filterField = storeFilterFieldEl.value.trim(); const filterOperator = storeFilterOperatorEl.value; const filterValue = storeFilterValueEl.value.trim(); if (filterField && filterValue) { // Handle array operators (IN, NIN, BTWN) if (['IN', 'NIN', 'BTWN'].includes(filterOperator)) { const valueArray = filterValue.split(',').map(v => v.trim()); query = query.where(filterField, filterOperator, valueArray); } else { // Convert to number if it looks like one const value = !isNaN(filterValue) ? parseFloat(filterValue) : filterValue; query = query.where(filterField, filterOperator, value); } } // Add sort if field is provided const sortField = storeSortFieldEl.value.trim(); const sortDirection = storeSortDirectionEl.value; if (sortField) { query = query.orderBy(sortField, sortDirection); } // Execute query const data = await query.get(); // Check response if (isErrorResponse(data)) { showStatus(`❌ Error: ${data.message || 'Store data not found'}`, 'error', 'store-tab'); showResults(data); return; } if (data) { if (Array.isArray(data)) { if (data.length > 0) { showStatus(`✅ Successfully retrieved store ${type}: (${data.length} items)`, 'success', 'store-tab'); } else { showStatus(`⚠️ Store ${type} found but contains no items.`, 'info', 'store-tab'); } } else if (typeof data === 'object' && Object.keys(data).length > 0) { showStatus(`✅ Successfully retrieved store ${type} data`, 'success', 'store-tab'); } else { showStatus('⚠️ Response received but with unexpected format.', 'info', 'store-tab'); } showResults(data); } else { showStatus('⚠️ Received empty response.', 'error', 'store-tab'); showResults({}); } } catch (error) { showStatus(`❌ Error finding store ${type}: ${error.message}`, 'error', 'store-tab'); showResults({ error: error.message }); } } // Load content library async function loadContentLibrary() { if (!apiState.isDudaSite) { showStatus('Cannot access content library: dmAPI not detected', 'error', 'content-library-tab'); return; } if (!apiState.contentLibraryAPI) { try { showStatus('Initializing Content Library API...', 'info', 'content-library-tab'); apiState.contentLibraryAPI = await window.dmAPI.loadContentLibrary(); } catch (error) { showStatus(`❌ Error initializing Content Library API: ${error.message}`, 'error', 'content-library-tab'); return; } } debug('Loading content library'); try { const data = apiState.contentLibraryAPI; if (!data) { showStatus('❌ Content Library returned no data', 'error', 'content-library-tab'); return; } // Display the data in each section displayContentLibraryData(data); showStatus('✅ Successfully loaded Content Library data', 'success', 'content-library-tab'); showResults(data); } catch (error) { showStatus(`❌ Error loading Content Library: ${error.message}`, 'error', 'content-library-tab'); showResults({ error: error.message }); } } // Display content library data in sections function displayContentLibraryData(data) { // Helper to create a simple key-value display function createDataDisplay(obj, depth = 0) { if (!obj || typeof obj !== 'object') { return document.createTextNode(obj === null ? 'null' : obj === undefined ? 'undefined' : String(obj)); } const container = document.createElement('div'); container.style.paddingLeft = depth > 0 ? '15px' : '0'; // Handle arrays if (Array.isArray(obj)) { if (obj.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '(empty array)'; emptyMsg.style.fontStyle = 'italic'; emptyMsg.style.color = '#888'; container.appendChild(emptyMsg); } else { obj.forEach((item, index) => { const itemContainer = document.createElement('div'); itemContainer.style.marginBottom = '8px'; const indexEl = document.createElement('div'); indexEl.textContent = `[${index}]:`; indexEl.style.fontWeight = 'bold'; itemContainer.appendChild(indexEl); itemContainer.appendChild(createDataDisplay(item, depth + 1)); container.appendChild(itemContainer); }); } return container; } // Handle objects const keys = Object.keys(obj); if (keys.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '(empty object)'; emptyMsg.style.fontStyle = 'italic'; emptyMsg.style.color = '#888'; container.appendChild(emptyMsg); } else { keys.forEach(key => { const row = document.createElement('div'); row.style.marginBottom = '8px'; const keyEl = document.createElement('div'); keyEl.textContent = `${key}:`; keyEl.style.fontWeight = 'bold'; row.appendChild(keyEl); const valueContainer = document.createElement('div'); valueContainer.style.marginLeft = '15px'; valueContainer.appendChild(createDataDisplay(obj[key], depth + 1)); row.appendChild(valueContainer); container.appendChild(row); }); } return container; } // Update each section document.querySelectorAll('.aog-js-content-section').forEach(section => { const sectionName = section.getAttribute('data-section'); const bodyEl = section.querySelector('.aog-js-content-section-body'); // Clear previous content bodyEl.innerHTML = ''; // Add new content if (data && data[sectionName]) { bodyEl.appendChild(createDataDisplay(data[sectionName])); } else { const notFoundEl = document.createElement('div'); notFoundEl.textContent = 'No data found for this section'; notFoundEl.style.fontStyle = 'italic'; notFoundEl.style.color = '#888'; notFoundEl.style.padding = '10px'; bodyEl.appendChild(notFoundEl); } }); } // Toggle content section function toggleContentSection(event) { const section = event.currentTarget.closest('.aog-js-content-section'); section.classList.toggle('expanded'); } // Check if current page is a dynamic page async function checkDynamicPage() { if (!apiState.isDudaSite) { showStatus('Cannot check dynamic page: dmAPI not detected', 'error', 'dynamic-pages-tab'); return; } try { if (!apiState.dynamicPagesAPI) { apiState.dynamicPagesAPI = window.dmAPI.dynamicPageApi(); } const isDynamic = apiState.dynamicPagesAPI.isDynamicPage(); isDynamicEl.textContent = isDynamic ? 'Yes ✓' : 'No'; isDynamicEl.style.color = isDynamic ? '#38a169' : '#666'; if (isDynamic) { showStatus('✅ This is a dynamic page. Loading page data...', 'success', 'dynamic-pages-tab'); try { const pageData = await apiState.dynamicPagesAPI.pageData(); showStatus('✅ Successfully loaded dynamic page data', 'success', 'dynamic-pages-tab'); showResults(pageData); // Display dynamic page info const infoEl = document.getElementById('aog-js-dynamic-page-info'); // Clear existing items except the first one const items = infoEl.querySelectorAll('.aog-js-info-item'); for (let i = 1; i < items.length; i++) { items[i].remove(); } // Add new info items if (pageData) { for (const key in pageData) { if (key !== 'data') { // Skip the data property, show it in raw JSON instead const item = document.createElement('div'); item.className = 'aog-js-info-item'; const label = document.createElement('div'); label.className = 'aog-js-info-label'; label.textContent = formatLabel(key) + ':'; const value = document.createElement('div'); value.className = 'aog-js-info-value'; value.textContent = typeof pageData[key] === 'object' ? JSON.stringify(pageData[key]).substring(0, 50) + '...' : pageData[key]; item.appendChild(label); item.appendChild(value); infoEl.appendChild(item); } } } } catch (error) { showStatus(`❌ Error loading dynamic page data: ${error.message}`, 'error', 'dynamic-pages-tab'); showResults({ error: error.message }); } } else { showStatus('❌ This is not a dynamic page', 'error', 'dynamic-pages-tab'); } } catch (error) { showStatus(`❌ Error checking dynamic page: ${error.message}`, 'error', 'dynamic-pages-tab'); isDynamicEl.textContent = 'Error'; isDynamicEl.style.color = '#e53e3e'; } } // Format label for display function formatLabel(str) { return str .replace(/_/g, ' ') .replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); } // Call core API method async function callCoreMethod(methodName) { if (!apiState.isDudaSite) { methodResultEl.textContent = 'Error: dmAPI not detected'; return; } debug(`Calling core method: ${methodName}`); try { let result; // Handle async methods differently if (methodName === 'getNavItemsAsync') { result = await window.dmAPI[methodName](); // Special handling for navigation data to make it more readable if (result) { methodResultEl.innerHTML = ''; // Create a formatted navigation display const navContainer = document.createElement('div'); navContainer.className = 'aog-js-nav-display'; function addNavItem(item, level = 0) { const itemEl = document.createElement('div'); itemEl.className = 'aog-js-nav-item'; itemEl.style.paddingLeft = `${level * 20}px`; const titleEl = document.createElement('span'); titleEl.className = 'aog-js-nav-title'; titleEl.textContent = item.title; const pathEl = document.createElement('span'); pathEl.className = 'aog-js-nav-path'; pathEl.textContent = ` (${item.path})`; const visibleEl = document.createElement('span'); visibleEl.className = 'aog-js-nav-visible'; visibleEl.textContent = item.visible ? ' - Visible' : ' - Hidden'; visibleEl.style.color = item.visible ? '#38a169' : '#e53e3e'; itemEl.appendChild(titleEl); itemEl.appendChild(pathEl); itemEl.appendChild(visibleEl); navContainer.appendChild(itemEl); // Add subnav items if any if (item.subNav && item.subNav.length > 0) { item.subNav.forEach(subItem => { addNavItem(subItem, level + 1); }); } } // Add heading const headingEl = document.createElement('div'); headingEl.className = 'aog-js-nav-heading'; headingEl.textContent = 'Navigation Structure:'; navContainer.appendChild(headingEl); // Process all nav items result.forEach(item => { addNavItem(item); }); methodResultEl.appendChild(navContainer); // Add link to raw JSON const rawLink = document.createElement('a'); rawLink.href = '#'; rawLink.textContent = 'Show raw JSON'; rawLink.className = 'aog-js-show-raw'; rawLink.addEventListener('click', (e) => { e.preventDefault(); methodResultEl.textContent = JSON.stringify(result, null, 2); }); const rawLinkContainer = document.createElement('div'); rawLinkContainer.className = 'aog-js-raw-link-container'; rawLinkContainer.appendChild(rawLink); methodResultEl.appendChild(rawLinkContainer); } else { methodResultEl.textContent = 'No navigation data returned'; } } else { // Call the method directly for non-async methods result = window.dmAPI[methodName](); // Format and display the result methodResultEl.textContent = typeof result === 'object' ? JSON.stringify(result, null, 2) : result; } // Also show in Raw JSON tab showResults(result); } catch (error) { methodResultEl.textContent = `Error: ${error.message}`; } } // Check if response contains an error message function isErrorResponse(data) { // Check for common error patterns in responses if (data && typeof data === 'object') { // Check for error message property if (data.message && typeof data.message === 'string') { const errorKeywords = ['error', 'no collection', 'not found', 'invalid', 'failed']; return errorKeywords.some(keyword => data.message.toLowerCase().includes(keyword)); } // Check for error property if (data.error || data.errorMessage || data.status === 'error') { return true; } } return false; } // Show results in raw JSON tab function showResults(data) { apiState.currentData = data; // Format and display in raw JSON tab if (rawJsonEl) { try { const formattedJson = JSON.stringify(data, null, 2); rawJsonEl.textContent = formattedJson; } catch (error) { rawJsonEl.textContent = 'Error formatting JSON data'; } } } // Switch to specific tab function switchToTab(tabId) { // Find the tab button const tabButton = document.querySelector(`.aog-js-tab-button[data-tab="${tabId}"]`); if (tabButton) { // Create a mock event object const event = { target: tabButton }; switchTab(event); } } // Copy JSON to clipboard function copyJsonToClipboard() { try { const text = rawJsonEl.textContent; navigator.clipboard.writeText(text).then(function() { showCopiedNotification(); }).catch(function(err) { debug('Clipboard API error:', err); // Fallback method for older browsers copyToClipboardFallback(text); }); } catch (err) { debug('Failed to copy:', err); } } // Format JSON function formatJson() { try { if (!apiState.currentData) { return; } const formatted = JSON.stringify(apiState.currentData, null, 2); rawJsonEl.textContent = formatted; } catch (error) { debug('Error formatting JSON:', error); } } // Fallback clipboard method for browsers without clipboard API function copyToClipboardFallback(text) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const successful = document.execCommand('copy'); if (successful) { showCopiedNotification(); } else { debug('Failed to copy text with execCommand'); } } catch (err) { debug('Error during execCommand copy:', err); } document.body.removeChild(textArea); } // Show copied notification function showCopiedNotification() { // Create notification if it doesn't exist let notification = document.querySelector('.aog-js-copied-notification'); if (!notification) { notification = document.createElement('div'); notification.className = 'aog-js-copied-notification'; notification.textContent = 'JSON copied to clipboard'; document.body.appendChild(notification); } // Show notification notification.classList.add('show'); // Hide notification after delay setTimeout(() => { notification.classList.remove('show'); }, 2000); } // Toggle debug mode function toggleDebugMode() { DEBUG = debugToggle.checked; debug('Debug mode toggled:', DEBUG); } // Discover Members API async function discoverMembersAPI() { updateApiSectionStatus('members', 'Checking...'); try { // Check if getLoggedInMember method exists if (typeof window.dmAPI.getLoggedInMember === 'function') { try { // Try to call the method await window.dmAPI.getLoggedInMember(); updateApiSectionStatus('members', 'Available', 'Members API active'); } catch (error) { // If we get a specific error about not being logged in, API is still available if (error && error.message && error.message.includes('not logged in')) { updateApiSectionStatus('members', 'Available', 'No member logged in'); } else { updateApiSectionStatus('members', 'Not Available', error.message); } } } else { updateApiSectionStatus('members', 'Not Available', 'Members API not found'); } } catch (error) { updateApiSectionStatus('members', 'Error', error.message); } } // Check for logged-in member async function checkLoggedInMember() { if (!apiState.isDudaSite) { showStatus('Cannot check member: dmAPI not detected', 'error', 'members-tab'); return; } const memberStatusEl = document.getElementById('aog-js-member-status'); const isLoggedInEl = document.getElementById('aog-js-is-logged-in'); const memberInfoEl = document.getElementById('aog-js-member-info'); try { showStatus('Checking for logged-in member...', 'info', 'members-tab'); if (typeof window.dmAPI.getLoggedInMember !== 'function') { showStatus('❌ Members API not available on this site', 'error', 'members-tab'); isLoggedInEl.textContent = 'API Not Available'; isLoggedInEl.style.color = '#e53e3e'; return; } try { const memberData = await window.dmAPI.getLoggedInMember(); // Check if this is a placeholder/default user or a real logged-in user const isPlaceholder = !memberData || (memberData.firstName === 'John' && memberData.lastName === 'Doe') || !memberData.uuid || Object.keys(memberData).length < 3; if (isPlaceholder) { isLoggedInEl.textContent = 'Not Logged In (Default Data)'; isLoggedInEl.style.color = '#dd6b20'; showStatus('ℹ️ No real member is logged in (placeholder data detected)', 'info', 'members-tab'); } else { // Real user is logged in isLoggedInEl.textContent = 'Logged In ✓'; isLoggedInEl.style.color = '#38a169'; showStatus('✅ Member is logged in, retrieved member data', 'success', 'members-tab'); } showResults(memberData); // Display member info // Clear existing items except the first one const items = memberInfoEl.querySelectorAll('.aog-js-info-item'); for (let i = 1; i < items.length; i++) { items[i].remove(); } // Add new info items if (memberData) { // Add a note if we think it's placeholder data if (isPlaceholder) { const placeholderNote = document.createElement('div'); placeholderNote.className = 'aog-js-placeholder-note'; placeholderNote.textContent = 'Note: This appears to be placeholder data, not a real logged-in user.'; placeholderNote.style.color = '#dd6b20'; placeholderNote.style.fontStyle = 'italic'; placeholderNote.style.marginBottom = '15px'; placeholderNote.style.padding = '8px'; placeholderNote.style.backgroundColor = '#fff5f5'; placeholderNote.style.borderRadius = '4px'; memberInfoEl.appendChild(placeholderNote); } for (const key in memberData) { const item = document.createElement('div'); item.className = 'aog-js-info-item'; const label = document.createElement('div'); label.className = 'aog-js-info-label'; label.textContent = formatLabel(key) + ':'; const value = document.createElement('div'); value.className = 'aog-js-info-value'; value.textContent = typeof memberData[key] === 'object' ? JSON.stringify(memberData[key]).substring(0, 50) + '...' : memberData[key]; item.appendChild(label); item.appendChild(value); memberInfoEl.appendChild(item); } } } catch (error) { if (error && error.message && error.message.includes('not logged in')) { isLoggedInEl.textContent = 'Not Logged In'; isLoggedInEl.style.color = '#dd6b20'; showStatus('ℹ️ No member is currently logged in', 'info', 'members-tab'); } else { isLoggedInEl.textContent = 'Error'; isLoggedInEl.style.color = '#e53e3e'; showStatus(`❌ Error checking member: ${error.message}`, 'error', 'members-tab'); } } } catch (error) { showStatus(`❌ Error: ${error.message}`, 'error', 'members-tab'); } } // Discover Sitemap async function discoverSitemap() { updateApiSectionStatus('sitemap', 'Checking...'); try { // Try to fetch the sitemap.xml file const domain = window.location.hostname; const sitemapUrl = `https://${domain}/sitemap.xml`; try { const response = await fetch(sitemapUrl, { method: 'HEAD' }); if (response.ok) { updateApiSectionStatus('sitemap', 'Available', 'Sitemap.xml found'); } else { updateApiSectionStatus('sitemap', 'Not Available', 'Sitemap.xml not found'); } } catch (error) { updateApiSectionStatus('sitemap', 'Not Available', 'Cannot access sitemap.xml'); } } catch (error) { updateApiSectionStatus('sitemap', 'Error', error.message); } } // Fetch and parse sitemap async function fetchSitemap() { if (!apiState.isDudaSite) { showStatus('Cannot fetch sitemap: Site not detected', 'error', 'sitemap-tab'); return; } const sitemapStatusEl = document.getElementById('aog-js-sitemap-status'); const sitemapStatsEl = document.getElementById('aog-js-sitemap-stats'); const sitemapCountEl = document.getElementById('aog-js-sitemap-count'); const sitemapUpdatedEl = document.getElementById('aog-js-sitemap-updated'); const sitemapContainerEl = document.getElementById('aog-js-sitemap-container'); const sitemapUrlsEl = document.getElementById('aog-js-sitemap-urls'); try { showStatus('Fetching sitemap.xml...', 'info', 'sitemap-tab'); const domain = window.location.hostname; const sitemapUrl = `https://${domain}/sitemap.xml`; try { // Try to fetch the sitemap directly const response = await fetch(sitemapUrl); if (response.ok) { const text = await response.text(); // Parse the XML const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, 'text/xml'); const urls = xmlDoc.getElementsByTagName('url'); if (urls.length > 0) { // Build site URL list sitemapUrlsEl.innerHTML = ''; const urlList = document.createElement('ul'); urlList.className = 'aog-js-sitemap-url-list'; // Extract URLs const siteUrls = []; for (let i = 0; i < urls.length; i++) { const loc = urls[i].getElementsByTagName('loc')[0]; if (loc) { siteUrls.push({ url: loc.textContent, lastmod: urls[i].getElementsByTagName('lastmod')[0]?.textContent }); } } // Sort by URL siteUrls.sort((a, b) => a.url.localeCompare(b.url)); // Add to list siteUrls.forEach(item => { const li = document.createElement('li'); li.className = 'aog-js-sitemap-url-item'; const urlEl = document.createElement('a'); urlEl.href = item.url; urlEl.target = '_blank'; urlEl.textContent = item.url.replace(`https://${domain}`, ''); li.appendChild(urlEl); if (item.lastmod) { const lastmodEl = document.createElement('span'); lastmodEl.className = 'aog-js-sitemap-lastmod'; lastmodEl.textContent = ` (${new Date(item.lastmod).toLocaleDateString()})`; li.appendChild(lastmodEl); } urlList.appendChild(li); }); sitemapUrlsEl.appendChild(urlList); // Update stats sitemapCountEl.textContent = urls.length; sitemapUpdatedEl.textContent = new Date().toLocaleDateString(); // Show the sections sitemapStatsEl.style.display = 'block'; sitemapContainerEl.style.display = 'block'; showStatus(`✅ Successfully retrieved sitemap with ${urls.length} URLs`, 'success', 'sitemap-tab'); showResults({ sitemapUrl: sitemapUrl, urlCount: urls.length, urls: siteUrls }); } else { showStatus('⚠️ Sitemap found but contains no URLs', 'info', 'sitemap-tab'); } } else { showStatus(`❌ Could not access sitemap.xml: ${response.status} ${response.statusText}`, 'error', 'sitemap-tab'); } } catch (error) { // If direct fetch fails, try a proxy approach or CORS workaround showStatus(`❌ Error fetching sitemap: ${error.message}`, 'error', 'sitemap-tab'); showStatus('Note: Direct access to sitemap.xml may be restricted by CORS. Try viewing it directly in your browser.', 'info', 'sitemap-tab'); } } catch (error) { showStatus(`❌ Error: ${error.message}`, 'error', 'sitemap-tab'); } } // Update API section status in discovery tab function updateApiSectionStatus(section, status, details = '') { const sectionEl = document.querySelector(`.aog-js-api-section[data-section="${section}"]`); if (!sectionEl) return; const statusEl = sectionEl.querySelector('.aog-js-api-section-status'); if (!statusEl) return; // Set status text statusEl.textContent = status + (details ? ': ' + details : ''); // Set status color if (status === 'Available' || status === 'Active') { statusEl.style.color = '#38a169'; } else if (status === 'Not Available') { statusEl.style.color = '#dd6b20'; } else if (status === 'Error') { statusEl.style.color = '#e53e3e'; } else { statusEl.style.color = '#666'; } } // Discover core API methods function discoverCoreAPI() { updateApiSectionStatus('core', 'Checking...'); try { const coreMethodsAvailable = []; // List of core methods to check const coreMethods = [ 'getSiteName', 'getSiteExternalId', 'getSitePlanID', 'getCurrentDeviceType', 'getCurrentEnvironment', 'getNavItemsAsync' ]; // Check which methods are available coreMethods.forEach(method => { if (typeof window.dmAPI[method] === 'function') { coreMethodsAvailable.push(method); } }); if (coreMethodsAvailable.length > 0) { updateApiSectionStatus('core', 'Available', `${coreMethodsAvailable.length} methods found`); // Display available methods const resultsEl = document.querySelector('.aog-js-api-section[data-section="core"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; coreMethodsAvailable.forEach(method => { const methodEl = document.createElement('div'); methodEl.className = 'aog-js-discovered-method'; methodEl.textContent = method; resultsEl.appendChild(methodEl); }); } else { updateApiSectionStatus('core', 'Not Available', 'No core methods found'); } } catch (error) { updateApiSectionStatus('core', 'Error', error.message); } } // Discover content library async function discoverContentLibrary() { updateApiSectionStatus('contentLibrary', 'Checking...'); try { if (!apiState.contentLibraryAPI) { try { apiState.contentLibraryAPI = await window.dmAPI.loadContentLibrary(); } catch (error) { updateApiSectionStatus('contentLibrary', 'Not Available', error.message); return; } } if (apiState.contentLibraryAPI) { const sections = []; // Check which content library sections have data if (apiState.contentLibraryAPI.business_data) sections.push('business_data'); if (apiState.contentLibraryAPI.location_data) sections.push('location_data'); if (apiState.contentLibraryAPI.additional_locations) sections.push('additional_locations'); if (apiState.contentLibraryAPI.site_texts) sections.push('site_texts'); if (apiState.contentLibraryAPI.site_images) sections.push('site_images'); updateApiSectionStatus('contentLibrary', 'Available', `${sections.length} sections with data`); // Display available sections const resultsEl = document.querySelector('.aog-js-api-section[data-section="contentLibrary"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; sections.forEach(section => { const sectionEl = document.createElement('div'); sectionEl.className = 'aog-js-discovered-item'; sectionEl.textContent = formatLabel(section); resultsEl.appendChild(sectionEl); }); } else { updateApiSectionStatus('contentLibrary', 'Not Available', 'No content library data found'); } } catch (error) { updateApiSectionStatus('contentLibrary', 'Error', error.message); } } // Discover store API async function discoverStoreAPI() { updateApiSectionStatus('store', 'Checking...'); try { if (!apiState.collectionsAPI) { try { apiState.collectionsAPI = await window.dmAPI.loadCollectionsAPI(); } catch (error) { updateApiSectionStatus('store', 'Not Available', error.message); return; } } // Check if storeData method exists if (typeof apiState.collectionsAPI.storeData === 'function') { try { // Try to get a product from the catalog const product = await apiState.collectionsAPI.storeData('catalog_product').pageSize(1).get(); const hasProducts = Array.isArray(product) && product.length > 0; // Try to get a category const category = await apiState.collectionsAPI.storeData('catalog_category').pageSize(1).get(); const hasCategories = Array.isArray(category) && category.length > 0; if (hasProducts || hasCategories) { updateApiSectionStatus('store', 'Available', 'Store API active'); // Display store data info const resultsEl = document.querySelector('.aog-js-api-section[data-section="store"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; if (hasProducts) { const productEl = document.createElement('div'); productEl.className = 'aog-js-discovered-item'; productEl.textContent = 'Product Catalog Available'; resultsEl.appendChild(productEl); } if (hasCategories) { const categoryEl = document.createElement('div'); categoryEl.className = 'aog-js-discovered-item'; categoryEl.textContent = 'Product Categories Available'; resultsEl.appendChild(categoryEl); } } else { updateApiSectionStatus('store', 'Available', 'No products or categories found'); } } catch (error) { updateApiSectionStatus('store', 'Not Available', 'Store API error: ' + error.message); } } else { updateApiSectionStatus('store', 'Not Available', 'Store API method not found'); } } catch (error) { updateApiSectionStatus('store', 'Error', error.message); } } // Discover dynamic pages async function discoverDynamicPages() { updateApiSectionStatus('dynamicPages', 'Checking...'); try { if (!apiState.dynamicPagesAPI) { try { apiState.dynamicPagesAPI = window.dmAPI.dynamicPageApi(); } catch (error) { updateApiSectionStatus('dynamicPages', 'Not Available', error.message); return; } } const isDynamic = apiState.dynamicPagesAPI.isDynamicPage(); if (isDynamic) { const pageData = await apiState.dynamicPagesAPI.pageData(); updateApiSectionStatus('dynamicPages', 'Active', 'Current page is a dynamic page'); // Display dynamic page info const resultsEl = document.querySelector('.aog-js-api-section[data-section="dynamicPages"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; if (pageData) { const keysEl = document.createElement('div'); keysEl.className = 'aog-js-discovered-item'; keysEl.textContent = `Available fields: ${Object.keys(pageData).join(', ')}`; resultsEl.appendChild(keysEl); } } else { updateApiSectionStatus('dynamicPages', 'Available', 'Current page is not a dynamic page'); } } catch (error) { updateApiSectionStatus('dynamicPages', 'Error', error.message); } } // Setup event listeners debug('Setting up event listeners'); // Main button toggle if (explorerButton) { explorerButton.addEventListener('click', togglePanel); } // Close button if (closeButton) { closeButton.addEventListener('click', closePanel); } // Initialize All APIs button if (initAllBtn) { initAllBtn.addEventListener('click', initAllAPIs); } // Content section toggles contentSectionElements.forEach(section => { const headerEl = section.querySelector('.aog-js-content-section-header'); if (headerEl) { headerEl.addEventListener('click', toggleContentSection); } }); // Find collection button if (findBtn) { findBtn.addEventListener('click', () => findCollection()); } // Collection input enter key if (collectionInput) { collectionInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { findCollection(); } }); } // Store buttons if (loadCatalogBtn) { loadCatalogBtn.addEventListener('click', () => findStoreData('catalog')); } if (loadCategoriesBtn) { loadCategoriesBtn.addEventListener('click', () => findStoreData('categories')); } // Content Library button if (loadContentLibraryBtn) { loadContentLibraryBtn.addEventListener('click', loadContentLibrary); } // Dynamic Pages button if (checkDynamicPageBtn) { checkDynamicPageBtn.addEventListener('click', checkDynamicPage); } // Members API button const checkMemberBtn = document.getElementById('aog-js-check-member'); if (checkMemberBtn) { checkMemberBtn.addEventListener('click', checkLoggedInMember); } // Sitemap button const fetchSitemapBtn = document.getElementById('aog-js-fetch-sitemap'); if (fetchSitemapBtn) { fetchSitemapBtn.addEventListener('click', fetchSitemap); } // Core method buttons methodButtons.forEach(button => { button.addEventListener('click', () => { const method = button.getAttribute('data-method'); if (method) { callCoreMethod(method); } }); }); // API Discovery button if (discoverAllBtn) { discoverAllBtn.addEventListener('click', discoverAPIs); } // Debug toggle if (debugToggle) { debugToggle.addEventListener('change', toggleDebugMode); } // Copy JSON button if (copyJsonBtn) { copyJsonBtn.addEventListener('click', copyJsonToClipboard); } // Format JSON button if (formatJsonBtn) { formatJsonBtn.addEventListener('click', formatJson); } // Tab switching tabButtons.forEach(button => { button.addEventListener('click', switchTab); }); // Show data section by default dataEl.style.display = 'block'; // Check if we're on a Duda site checkDudaEnvironment(); // Apply style updates applyStyleUpdates(); } // Initialize on load if (document.readyState === 'complete' || document.readyState === 'interactive') { // Document already loaded, initialize immediately initEnhancedJSAPIExplorer(); } else { // Wait for document to load document.addEventListener('DOMContentLoaded', initEnhancedJSAPIExplorer); } })();
<div id="snippet-2od8g29h"> <!-- HTML SECTION START --> <!-- Enhanced Duda JS API Explorer Widget This is a self-contained HTML widget that provides a comprehensive interface for exploring the full Duda JS API including Collections, Store API, Dynamic Pages, Content Library and core API methods. Created: May 2025 --> <div id="aog-enhanced-js-explorer-widget"> <!-- Widget Button --> <button id="aog-js-explorer-button" class="aog-js-explorer-button" type="button"> <span class="aog-js-explorer-icon">🔍</span> <span class="aog-js-explorer-text">JS API Explorer</span> </button> <!-- Widget Panel --> <div id="aog-js-explorer-panel" class="aog-js-explorer-panel"> <div class="aog-js-explorer-header"> <div class="aog-js-logo-container"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/Group+206.svg" alt="Agents of Genius" class="aog-js-logo"> </div> <h5 class="aog-js-title">JS API Explorer</h5> <div class="aog-js-explorer-controls"> <button id="aog-js-explorer-close" class="aog-js-explorer-close" type="button">×</button> </div> </div> <div class="aog-js-explorer-content"> <div id="aog-js-explorer-loading" class="aog-js-explorer-loading"> <div class="aog-js-spinner"></div> <div>Initializing API...</div> </div> <div id="aog-js-explorer-error" class="aog-js-explorer-error"> <div class="aog-js-error-icon">⚠️</div> <div> <strong>Error retrieving data</strong> <p id="aog-js-error-message">Could not load API data. Please try again.</p> </div> </div> <div id="aog-js-explorer-data"> <!-- Tabs Navigation --> <div class="aog-js-explorer-tabs"> <button class="aog-js-tab-button active" data-tab="api-discovery" type="button">API Discovery</button> <button class="aog-js-tab-button" data-tab="collections" type="button">Collections</button> <button class="aog-js-tab-button" data-tab="content-library" type="button">Content Library</button> <button class="aog-js-tab-button" data-tab="store" type="button">Store</button> <button class="aog-js-tab-button" data-tab="dynamic-pages" type="button">Dynamic Pages</button> <button class="aog-js-tab-button" data-tab="members" type="button">Members</button> <button class="aog-js-tab-button" data-tab="sitemap" type="button">Sitemap</button> <button class="aog-js-tab-button" data-tab="core-methods" type="button">Core Methods</button> <button class="aog-js-tab-button" data-tab="raw-json" type="button">Raw JSON</button> <button class="aog-js-tab-button" data-tab="api-settings" type="button">Settings</button> </div> <!-- Tab Content --> <div class="aog-js-explorer-tab-content"> <!-- API Discovery Tab --> <div id="api-discovery-tab" class="aog-js-tab-pane active"> <h5 class="aog-js-section-title">API Discovery</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Scan this Duda site to discover available API features and data sources.</p> <p>This will check for Collections, Content Library data, Store information, Dynamic Pages, and Members API.</p> </div> <button id="aog-js-discover-all" class="aog-js-primary-button"> <span class="aog-js-button-icon">🔍</span> Discover Available APIs </button> <div id="aog-js-discovery-results" class="aog-js-discovery-results"> <div class="aog-js-api-section" data-section="core"> <h5 class="aog-js-api-section-title">Core API</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="collections"> <h5 class="aog-js-api-section-title">Collections</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="contentLibrary"> <h5 class="aog-js-api-section-title">Content Library</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="store"> <h5 class="aog-js-api-section-title">Store API</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="dynamicPages"> <h5 class="aog-js-api-section-title">Dynamic Pages</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="members"> <h5 class="aog-js-api-section-title">Members API</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> <div class="aog-js-api-section" data-section="sitemap"> <h5 class="aog-js-api-section-title">Sitemap</h5> <div class="aog-js-api-section-status">Not checked</div> <div class="aog-js-api-section-results"></div> </div> </div> </div> </div> </div> <!-- Collections Tab --> <div id="collections-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Collection Explorer</h5> <div class="aog-js-input-container"> <div class="aog-js-collection-actions"> <button id="aog-js-find-all-btn" class="aog-js-primary-button aog-js-secondary-button"> <span class="aog-js-button-icon">🔍</span> Find All Collections </button> <div class="aog-js-specific-collection"> <label for="aog-js-collection-input" class="aog-js-label">Specific Collection Name/ID:</label> <div class="aog-js-input-group"> <input id="aog-js-collection-input" type="text" placeholder="Enter collection name or ID" class="aog-js-input"> <button id="aog-js-find-btn" class="aog-js-action-button">Find</button> </div> </div> </div> </div> <div id="aog-js-collections-status" class="aog-js-status"></div> <div id="aog-js-discovered-collections" class="aog-js-discovered-collections"> <div class="aog-js-collections-header"> <div class="aog-js-section-subtitle">Discovered Collections</div> <div class="aog-js-discovery-count">0 found</div> </div> <div id="aog-js-collections-list" class="aog-js-collections-list"> <!-- Collections will be populated here --> <div class="aog-js-empty-state">No collections discovered yet. Use "API Discovery" to scan the page.</div> </div> </div> <div class="aog-js-filter-section"> <div class="aog-js-section-subtitle">Filtering & Sorting (Optional)</div> <div class="aog-js-filter-inputs"> <div class="aog-js-input-row"> <label for="aog-js-filter-field" class="aog-js-label">Field:</label> <input id="aog-js-filter-field" type="text" placeholder="Field name" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-filter-operator" class="aog-js-label">Operator:</label> <select id="aog-js-filter-operator" class="aog-js-input"> <option value="EQ">Equal to (EQ)</option> <option value="NE">Not equal to (NE)</option> <option value="GT">Greater than (GT)</option> <option value="GTE">Greater than or equal (GTE)</option> <option value="LT">Less than (LT)</option> <option value="LTE">Less than or equal (LTE)</option> <option value="IN">In array (IN)</option> <option value="NIN">Not in array (NIN)</option> <option value="BTWN">Between (BTWN)</option> </select> </div> <div class="aog-js-input-row"> <label for="aog-js-filter-value" class="aog-js-label">Value:</label> <input id="aog-js-filter-value" type="text" placeholder="Value or comma-separated values for IN/BTWN" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-sort-field" class="aog-js-label">Sort by:</label> <input id="aog-js-sort-field" type="text" placeholder="Field name (optional)" class="aog-js-input"> <select id="aog-js-sort-direction" class="aog-js-input aog-js-small-input"> <option value="asc">Ascending</option> <option value="desc">Descending</option> </select> </div> <div class="aog-js-input-row"> <label for="aog-js-page-size" class="aog-js-label">Page size:</label> <input id="aog-js-page-size" type="number" value="50" min="1" max="100" class="aog-js-input aog-js-small-input"> <label for="aog-js-page-number" class="aog-js-label aog-js-inline-label">Page:</label> <input id="aog-js-page-number" type="number" value="0" min="0" class="aog-js-input aog-js-small-input"> </div> </div> </div> </div> <!-- Content Library Tab --> <div id="content-library-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Content Library Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Explore site's business info, text and images from the Content Library API.</p> </div> <button id="aog-js-load-content-library" class="aog-js-primary-button"> <span class="aog-js-button-icon">📚</span> Load Content Library </button> <div id="aog-js-content-lib-status" class="aog-js-status"></div> <div id="aog-js-content-sections" class="aog-js-content-sections"> <div class="aog-js-content-section-list"> <div class="aog-js-content-section" data-section="business_data"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Business Data</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="location_data"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Location Data</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="additional_locations"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Additional Locations</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="site_texts"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Site Texts</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> <div class="aog-js-content-section" data-section="site_images"> <div class="aog-js-content-section-header"> <span class="aog-js-content-section-title">Site Images</span> <span class="aog-js-content-section-toggle">▶</span> </div> <div class="aog-js-content-section-body"></div> </div> </div> </div> </div> </div> </div> <!-- Store Tab --> <div id="store-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Store API Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Explore the Store API to access product catalog and categories.</p> </div> <div class="aog-js-store-actions"> <button id="aog-js-load-catalog" class="aog-js-action-button aog-js-wide-button"> <span class="aog-js-button-icon">🛒</span> Load Product Catalog </button> <button id="aog-js-load-categories" class="aog-js-action-button aog-js-wide-button"> <span class="aog-js-button-icon">📁</span> Load Product Categories </button> </div> <div id="aog-js-store-status" class="aog-js-status"></div> <div class="aog-js-filter-section"> <div class="aog-js-section-subtitle">Filtering & Sorting (Optional)</div> <div class="aog-js-filter-inputs"> <div class="aog-js-input-row"> <label for="aog-js-store-filter-field" class="aog-js-label">Field:</label> <input id="aog-js-store-filter-field" type="text" placeholder="Field name" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-store-filter-operator" class="aog-js-label">Operator:</label> <select id="aog-js-store-filter-operator" class="aog-js-input"> <option value="EQ">Equal to (EQ)</option> <option value="NE">Not equal to (NE)</option> <option value="GT">Greater than (GT)</option> <option value="GTE">Greater than or equal (GTE)</option> <option value="LT">Less than (LT)</option> <option value="LTE">Less than or equal (LTE)</option> <option value="IN">In array (IN)</option> <option value="NIN">Not in array (NIN)</option> <option value="BTWN">Between (BTWN)</option> </select> </div> <div class="aog-js-input-row"> <label for="aog-js-store-filter-value" class="aog-js-label">Value:</label> <input id="aog-js-store-filter-value" type="text" placeholder="Value or comma-separated values for IN/BTWN" class="aog-js-input"> </div> <div class="aog-js-input-row"> <label for="aog-js-store-sort-field" class="aog-js-label">Sort by:</label> <input id="aog-js-store-sort-field" type="text" placeholder="Field name (optional)" class="aog-js-input"> <select id="aog-js-store-sort-direction" class="aog-js-input aog-js-small-input"> <option value="asc">Ascending</option> <option value="desc">Descending</option> </select> </div> </div> </div> </div> </div> </div> <!-- Dynamic Pages Tab --> <div id="dynamic-pages-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Dynamic Pages Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Check if the current page is a dynamic page and explore its data.</p> </div> <button id="aog-js-check-dynamic-page" class="aog-js-primary-button"> <span class="aog-js-button-icon">📄</span> Check Current Page </button> <div id="aog-js-dynamic-page-status" class="aog-js-status"></div> <div id="aog-js-dynamic-page-info" class="aog-js-dynamic-page-info"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Is Dynamic Page:</div> <div id="aog-js-is-dynamic" class="aog-js-info-value">Unknown</div> </div> </div> </div> </div> </div> <!-- Core Methods Tab --> <div id="core-methods-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Core API Methods</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Access core API methods to get site information, navigation items, and more.</p> </div> <div class="aog-js-method-group"> <h3 class="aog-js-method-group-title">Site Information</h3> <div class="aog-js-method-buttons"> <button class="aog-js-method-button" data-method="getSiteName">getSiteName</button> <button class="aog-js-method-button" data-method="getSiteExternalId">getSiteExternalId</button> <button class="aog-js-method-button" data-method="getSitePlanID">getSitePlanID</button> <button class="aog-js-method-button" data-method="getCurrentDeviceType">getCurrentDeviceType</button> <button class="aog-js-method-button" data-method="getCurrentEnvironment">getCurrentEnvironment</button> </div> </div> <div class="aog-js-method-group"> <h3 class="aog-js-method-group-title">Navigation</h3> <div class="aog-js-method-buttons"> <button class="aog-js-method-button" data-method="getNavItemsAsync">getNavItemsAsync</button> </div> </div> <div id="aog-js-method-result-container" class="aog-js-method-result-container"> <div class="aog-js-section-subtitle">Method Result</div> <pre id="aog-js-method-result" class="aog-js-method-result">Click a method button to see its result</pre> </div> </div> </div> </div> <!-- Raw JSON Tab --> <div id="raw-json-tab" class="aog-js-tab-pane"> <div class="aog-js-code-controls"> <button id="aog-js-copy-json" class="aog-js-control-button" type="button">Copy JSON</button> <button id="aog-js-format-json" class="aog-js-control-button" type="button">Format JSON</button> </div> <pre id="aog-js-raw-json" class="aog-js-json-display">No data available. Use the other tabs to fetch data first.</pre> </div> <!-- Members API Tab --> <div id="members-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Members API Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Check for logged-in members and access member data.</p> </div> <button id="aog-js-check-member" class="aog-js-primary-button"> <span class="aog-js-button-icon">👤</span> Check Logged-in Member </button> <div id="aog-js-member-status" class="aog-js-status"></div> <div id="aog-js-member-info" class="aog-js-member-info"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Login Status:</div> <div id="aog-js-is-logged-in" class="aog-js-info-value">Unknown</div> </div> </div> </div> </div> </div> <!-- Sitemap Tab --> <div id="sitemap-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">Sitemap Explorer</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Fetch and explore the site's sitemap.xml file.</p> </div> <button id="aog-js-fetch-sitemap" class="aog-js-primary-button"> <span class="aog-js-button-icon">🗺️</span> Fetch Sitemap </button> <div id="aog-js-sitemap-status" class="aog-js-status"></div> <div id="aog-js-sitemap-stats" class="aog-js-sitemap-stats" style="display: none;"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Total URLs:</div> <div id="aog-js-sitemap-count" class="aog-js-info-value">0</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Last Updated:</div> <div id="aog-js-sitemap-updated" class="aog-js-info-value">Unknown</div> </div> </div> <div id="aog-js-sitemap-container" class="aog-js-sitemap-container" style="display: none;"> <h5 class="aog-js-section-subtitle">Sitemap URLs</h5> <div id="aog-js-sitemap-urls" class="aog-js-sitemap-urls"></div> </div> </div> </div> </div> <!-- API Settings Tab --> <div id="api-settings-tab" class="aog-js-tab-pane"> <h5 class="aog-js-section-title">API Configuration</h5> <div class="aog-js-card"> <div class="aog-js-card-body"> <div class="aog-js-notice"> <p>Initialize the APIs to explore available data on this page.</p> </div> <button id="aog-js-init-all-btn" class="aog-js-primary-button"> <span class="aog-js-button-icon">⚡</span> Initialize All APIs </button> <div class="aog-js-api-info"> <div class="aog-js-info-item"> <div class="aog-js-info-label">Collections API:</div> <div id="aog-js-collections-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Content Library:</div> <div id="aog-js-content-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Dynamic Pages API:</div> <div id="aog-js-dynamic-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Members API:</div> <div id="aog-js-members-api-status" class="aog-js-info-value">Not initialized</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Core dmAPI:</div> <div id="aog-js-core-api-status" class="aog-js-info-value"> <span id="aog-js-core-api-available">Checking...</span> </div> </div> </div> <div class="aog-js-debug-section"> <div class="aog-js-section-subtitle">Debug Settings</div> <div class="aog-js-debug-toggle"> <label class="aog-js-switch"> <input type="checkbox" id="aog-js-debug-toggle"> <span class="aog-js-slider"></span> </label> <span class="aog-js-debug-label">Enable debug mode</span> </div> <p class="aog-js-debug-description"> When debug mode is enabled, detailed logs will be output to the browser console. This helps troubleshoot issues with API connections and data retrieval. </p> </div> </div> </div> </div> </div> </div> </div> </div> </div> <!-- HTML SECTION END --> <!-- CSS SECTION START --> <style> /* * Enhanced Duda JS API Explorer Widget - Styles * Uses Inter font family and adaptive colors */ /* Import Inter font */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); /* Define nav display styles */ .aog-js-nav-display { background-color: #f8f9fa; border-radius: 6px; padding: 12px; margin-bottom: 15px; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-nav-heading { font-weight: bold; margin-bottom: 10px; font-size: 15px; color: #1A1B4B; } .aog-js-nav-item { padding: 6px 0; border-bottom: 1px solid #eee; } .aog-js-nav-title { font-weight: 500; color: #1A1B4B; } .aog-js-nav-path { color: #666; font-size: 13px; } .aog-js-raw-link-container { text-align: right; padding-top: 10px; } .aog-js-show-raw { font-size: 13px; color: #6B46C1; text-decoration: none; } .aog-js-show-raw:hover { text-decoration: underline; } /* Widget Container */ #aog-enhanced-js-explorer-widget { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.5; color: #333; position: relative; z-index: 999999; } /* Button Styles */ .aog-js-explorer-button { position: fixed; bottom: 20px; left: 20px; background-color: #ad24a6; /* Agents of Genius CTA color */ color: white; border: none; border-radius: 50px; padding: 12px 20px; font-size: 14px; font-weight: 500; cursor: pointer; display: flex; align-items: center; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: all 0.3s ease; z-index: 999999; } .aog-js-explorer-button:hover { background-color: #851c83; /* Agents of Genius CTA hover color */ box-shadow: 0 4px 12px rgba(0,0,0,0.3); } .aog-js-explorer-icon { margin-right: 8px; font-weight: bold; font-size: 16px; } /* Panel Styles */ .aog-js-explorer-panel { position: fixed; bottom: 90px; left: 20px; width: 800px; height: 80vh; max-height: 800px; background-color: white; border-radius: 10px; /* Changed to 10px as requested */ box-shadow: 0 5px 25px rgba(0,0,0,0.3); display: none; flex-direction: column; z-index: 999998; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-explorer-header { background-color: #ad24a6; /* Agents of Genius CTA color */ padding: 15px 20px; border-bottom: 1px solid rgba(255,255,255,0.2); display: flex; justify-content: space-between; align-items: center; border-top-left-radius: 10px; border-top-right-radius: 10px; } .aog-js-logo-container { display: flex; align-items: center; height: 32px; } .aog-js-logo { height: 100%; max-width: 150px; } .aog-js-title { margin: 0; font-size: 16px; font-weight: 600; color: white !important; /* White text for better contrast */ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-explorer-controls { display: flex; align-items: center; } .aog-js-explorer-close { background: none; border: none; font-size: 24px; color: white; cursor: pointer; padding: 0 5px; line-height: 1; } .aog-js-explorer-content { padding: 0; overflow-y: auto; flex-grow: 1; position: relative; } /* Loading state */ .aog-js-explorer-loading { display: none; flex-direction: column; justify-content: center; align-items: center; height: 100%; padding: 30px; text-align: center; color: #666; } .aog-js-spinner { width: 40px; height: 40px; border: 3px solid #f3f3f3; border-top: 3px solid #ad24a6; /* Agents of Genius CTA color */ border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 15px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Error state */ .aog-js-explorer-error { display: none; padding: 30px; text-align: center; color: #e74c3c; flex-direction: column; align-items: center; justify-content: center; height: 100%; } .aog-js-error-icon { font-size: 40px; margin-bottom: 15px; } /* Tab navigation */ .aog-js-explorer-tabs { display: flex; border-bottom: 1px solid #E5E9F2; background-color: #F5F7FB; overflow-x: auto; white-space: nowrap; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-tab-button { padding: 12px 15px; background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 14px; font-weight: 500; color: #666; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-tab-button:hover { color: #ad24a6; /* Agents of Genius CTA color */ } .aog-js-tab-button.active { color: #ad24a6; /* Agents of Genius CTA color */ border-bottom-color: #ad24a6; /* Agents of Genius CTA color */ } /* Tab content */ .aog-js-explorer-tab-content { padding: 20px; } .aog-js-tab-pane { display: none; } .aog-js-tab-pane.active { display: block; } /* General data styles */ .aog-js-section-title { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 600; margin-bottom: 20px; color: #1A1B4B; } .aog-js-section-subtitle { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 600; color: #1A1B4B; margin-bottom: 12px; margin-top: 20px; } .aog-js-notice { padding: 10px; background-color: #f8f9fa; border-left: 4px solid #ad24a6; /* Agents of Genius CTA color */ margin-bottom: 15px; font-size: 13px; color: #666; } .aog-js-notice p { margin: 5px 0; } /* Input styles */ .aog-js-input-container { margin-bottom: 20px; } .aog-js-label { display: block; margin-bottom: 8px; font-weight: 500; color: #444; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-inline-label { display: inline-block; margin: 0 10px 0 15px; } .aog-js-input-group { display: flex; gap: 8px; } .aog-js-input { flex-grow: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-small-input { flex-grow: 0; width: 100px; } .aog-js-input:focus { outline: none; border-color: #ad24a6; } .aog-js-input-row { margin-bottom: 12px; display: flex; align-items: center; } .aog-js-filter-section { margin-top: 20px; border-top: 1px solid #eee; padding-top: 20px; } .aog-js-filter-inputs { background-color: #f8f9fa; padding: 15px; border-radius: 6px; } .aog-js-action-button { background-color: #333; color: white; border: none; border-radius: 6px; padding: 0 15px; font-weight: 500; cursor: pointer; transition: all 0.2s; height: 38px; display: flex; align-items: center; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-action-button:hover { background-color: #222; } .aog-js-wide-button { padding: 10px 20px; margin-right: 10px; margin-bottom: 10px; } .aog-js-store-actions { display: flex; flex-wrap: wrap; } /* Status indicator */ .aog-js-status { margin-bottom: 20px; padding: 12px 15px; border-radius: 6px; font-size: 14px; line-height: 1.4; display: none; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-status.success { background-color: #f0fff4; border-left: 3px solid #38a169; color: #2f855a; } .aog-js-status.error { background-color: #fff5f5; border-left: 3px solid #e53e3e; color: #c53030; } .aog-js-status.info { background-color: #f8f9fa; border-left: 3px solid #4299e1; color: #2b6cb0; } /* Discovered collections */ .aog-js-discovered-collections { margin-bottom: 20px; } .aog-js-collections-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } .aog-js-discovery-count { font-size: 13px; color: #666; background-color: #f0f0f0; padding: 4px 8px; border-radius: 12px; } .aog-js-collections-list { background-color: #f8f9fa; border-radius: 6px; padding: 15px; max-height: 200px; overflow-y: auto; } .aog-js-collection-item { padding: 10px; background-color: white; border-radius: 4px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: all 0.2s; } .aog-js-collection-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .aog-js-collection-id { font-weight: 500; color: #333; } .aog-js-collection-load { color: #ad24a6; font-size: 13px; font-weight: 500; } .aog-js-empty-state { padding: 20px; text-align: center; color: #888; font-style: italic; } /* Card styles */ .aog-js-card { background-color: #ffffff; border-radius: 8px; margin-bottom: 20px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.05); border: 1px solid #E5E9F2; } .aog-js-card-body { padding: 20px; } /* Button styles */ .aog-js-primary-button { background-color: #ad24a6; color: white; border: none; border-radius: 6px; padding: 12px 20px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; width: 100%; margin-bottom: 15px; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-primary-button:hover { background-color: #851c83; } .aog-js-button-icon { margin-right: 8px; } /* API Info */ .aog-js-api-info { margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px; } .aog-js-info-item { display: flex; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 10px; } .aog-js-info-label { width: 120px; font-weight: 500; color: #444; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-info-value { flex-grow: 1; color: #666; } /* JSON display */ .aog-js-json-display { background-color: #f8f9fa; padding: 15px; border-radius: 6px; overflow: auto; font-family: Monaco, Menlo, Consolas, "Courier New", monospace; font-size: 13px; line-height: 1.5; color: #333; white-space: pre-wrap; max-height: 600px; } .aog-js-code-controls { margin-bottom: 10px; display: flex; gap: 8px; } .aog-js-control-button { background-color: #f1f3f5; border: 1px solid #dee2e6; border-radius: 4px; padding: 6px 12px; font-size: 13px; cursor: pointer; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-control-button:hover { background-color: #e9ecef; } /* Copied notification */ .aog-js-copied-notification { position: fixed; top: 20px; right: 20px; background-color: #ad24a6; color: white; padding: 12px 20px; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); opacity: 0; transform: translateY(-10px); transition: all 0.3s ease; pointer-events: none; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-copied-notification.show { opacity: 1; transform: translateY(0); } /* Core Methods */ .aog-js-method-group { margin-bottom: 20px; } .aog-js-method-group-title { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; margin-bottom: 10px; color: #1A1B4B; } .aog-js-method-buttons { display: flex; flex-wrap: wrap; gap: 8px; } .aog-js-method-button { background-color: #f1f3f5; border: 1px solid #dee2e6; border-radius: 4px; padding: 8px 12px; font-size: 13px; cursor: pointer; transition: all 0.2s; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-method-button:hover { background-color: #e9ecef; } .aog-js-method-result-container { margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px; } .aog-js-method-result { background-color: #f8f9fa; padding: 15px; border-radius: 6px; overflow: auto; font-family: Monaco, Menlo, Consolas, "Courier New", monospace; font-size: 13px; line-height: 1.5; color: #666; white-space: pre-wrap; max-height: 200px; } /* API Discovery Results */ .aog-js-discovery-results { margin-top: 20px; } .aog-js-api-section { border: 1px solid #e5e9f2; border-radius: 6px; margin-bottom: 15px; overflow: hidden; } .aog-js-api-section-title { margin: 0; font-size: 15px; font-weight: 500; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-api-section h5 { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 18px; background-color: #f5f7fb; padding: 10px 15px; border-bottom: 1px solid #e5e9f2; display: flex; justify-content: space-between; align-items: center; margin: 0; color: #1A1B4B; } .aog-js-api-section-status { font-size: 13px; color: #666; font-weight: normal; } .aog-js-api-section-results { padding: 15px; max-height: 200px; overflow-y: auto; } /* Content Library */ .aog-js-content-sections { margin-top: 20px; } .aog-js-content-section { border: 1px solid #e5e9f2; border-radius: 6px; margin-bottom: 10px; overflow: hidden; } /* Collection actions */ .aog-js-collection-actions { display: flex; flex-direction: column; gap: 15px; width: 100%; } .aog-js-secondary-button { background-color: #6B46C1; margin-bottom: 5px; } .aog-js-secondary-button:hover { background-color: #553C9A; } .aog-js-specific-collection { width: 100%; } .aog-js-content-section-header { background-color: #f5f7fb; padding: 10px 15px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } .aog-js-content-section-title { font-weight: 500; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-content-section-body { padding: 0; max-height: 0; overflow: hidden; transition: all 0.3s ease; } .aog-js-content-section.expanded .aog-js-content-section-body { padding: 15px; max-height: 500px; overflow-y: auto; } .aog-js-content-section.expanded .aog-js-content-section-toggle { transform: rotate(90deg); } .aog-js-content-section-toggle { transition: transform 0.3s ease; } /* Debug Toggle */ .aog-js-debug-section { margin-top: 20px; border-top: 1px solid #eee; padding-top: 15px; } .aog-js-debug-toggle { display: flex; align-items: center; } .aog-js-switch { position: relative; display: inline-block; width: 50px; height: 24px; margin-right: 10px; } .aog-js-switch input { opacity: 0; width: 0; height: 0; } .aog-js-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; } .aog-js-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .aog-js-slider { background-color: #ad24a6; } input:checked + .aog-js-slider:before { transform: translateX(26px); } .aog-js-debug-label { font-size: 14px; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-debug-description { font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 13px; color: #666; line-height: 1.5; } /* Mobile responsiveness */ @media (max-width: 992px) { .aog-js-explorer-panel { width: calc(100% - 40px); height: 80vh; bottom: 80px; } .aog-js-explorer-tabs { overflow-x: auto; white-space: nowrap; } .aog-js-input-row { flex-direction: column; align-items: flex-start; } .aog-js-inline-label { margin: 8px 0; } .aog-js-store-actions { flex-direction: column; } .aog-js-wide-button { width: 100%; margin-right: 0; } } @media (max-width: 480px) { .aog-js-method-buttons { flex-direction: column; } .aog-js-method-button { width: 100%; } } /* Sitemap Styles */ .aog-js-sitemap-url-list { list-style: none; padding: 0; margin: 0; } .aog-js-sitemap-url-item { padding: 8px 0; border-bottom: 1px solid #eee; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .aog-js-sitemap-url-item a { color: #4a5568; text-decoration: none; } .aog-js-sitemap-url-item a:hover { color: #ad24a6; text-decoration: underline; } .aog-js-sitemap-lastmod { font-size: 12px; color: #718096; margin-left: 8px; } </style> <!-- CSS SECTION END --> <!-- JAVASCRIPT SECTION START --> <script> // Enhanced JS API Explorer Widget - Agents of Genius Branded JavaScript // IIFE to avoid polluting global scope (function() { // Debug flag - can be toggled via UI let DEBUG = false; // Debug logging function function debug(message, data) { if (DEBUG) { console.log(`[AOGJS Debug] ${message}`, data || ''); } } // Initialize immediately debug('Script loaded, initializing...'); document.addEventListener('DOMContentLoaded', initEnhancedJSAPIExplorer); // Main initialization function function initEnhancedJSAPIExplorer() { // API State let apiState = { collectionsAPI: null, contentLibraryAPI: null, dynamicPagesAPI: null, currentData: null, discoveredCollections: [], isDudaSite: false }; // DOM Elements const explorerButton = document.getElementById('aog-js-explorer-button'); const explorerPanel = document.getElementById('aog-js-explorer-panel'); const closeButton = document.getElementById('aog-js-explorer-close'); const loadingEl = document.getElementById('aog-js-explorer-loading'); const errorEl = document.getElementById('aog-js-explorer-error'); const errorMessage = document.getElementById('aog-js-error-message'); const dataEl = document.getElementById('aog-js-explorer-data'); // Settings Elements const initAllBtn = document.getElementById('aog-js-init-all-btn'); const collectionsApiStatusEl = document.getElementById('aog-js-collections-api-status'); const contentApiStatusEl = document.getElementById('aog-js-content-api-status'); const dynamicApiStatusEl = document.getElementById('aog-js-dynamic-api-status'); const coreApiAvailableEl = document.getElementById('aog-js-core-api-available'); const debugToggle = document.getElementById('aog-js-debug-toggle'); // Collections Tab Elements const findBtn = document.getElementById('aog-js-find-btn'); // Removed findAllBtn reference since we're removing that button const collectionInput = document.getElementById('aog-js-collection-input'); const collectionsStatusEl = document.getElementById('aog-js-collections-status'); const collectionsListEl = document.getElementById('aog-js-collections-list'); const discoveryCountEl = document.querySelector('.aog-js-discovery-count'); // Filter Elements const filterFieldEl = document.getElementById('aog-js-filter-field'); const filterOperatorEl = document.getElementById('aog-js-filter-operator'); const filterValueEl = document.getElementById('aog-js-filter-value'); const sortFieldEl = document.getElementById('aog-js-sort-field'); const sortDirectionEl = document.getElementById('aog-js-sort-direction'); const pageSizeEl = document.getElementById('aog-js-page-size'); const pageNumberEl = document.getElementById('aog-js-page-number'); // Store Tab Elements const loadCatalogBtn = document.getElementById('aog-js-load-catalog'); const loadCategoriesBtn = document.getElementById('aog-js-load-categories'); const storeStatusEl = document.getElementById('aog-js-store-status'); const storeFilterFieldEl = document.getElementById('aog-js-store-filter-field'); const storeFilterOperatorEl = document.getElementById('aog-js-store-filter-operator'); const storeFilterValueEl = document.getElementById('aog-js-store-filter-value'); const storeSortFieldEl = document.getElementById('aog-js-store-sort-field'); const storeSortDirectionEl = document.getElementById('aog-js-store-sort-direction'); // Content Library Tab Elements const loadContentLibraryBtn = document.getElementById('aog-js-load-content-library'); const contentLibStatusEl = document.getElementById('aog-js-content-lib-status'); const contentSectionElements = document.querySelectorAll('.aog-js-content-section'); // Dynamic Pages Tab Elements const checkDynamicPageBtn = document.getElementById('aog-js-check-dynamic-page'); const dynamicPageStatusEl = document.getElementById('aog-js-dynamic-page-status'); const isDynamicEl = document.getElementById('aog-js-is-dynamic'); // Core Methods Tab Elements const methodButtons = document.querySelectorAll('.aog-js-method-button'); const methodResultEl = document.getElementById('aog-js-method-result'); // API Discovery Tab Elements const discoverAllBtn = document.getElementById('aog-js-discover-all'); const apiSections = document.querySelectorAll('.aog-js-api-section'); // Raw JSON Tab Elements const rawJsonEl = document.getElementById('aog-js-raw-json'); const copyJsonBtn = document.getElementById('aog-js-copy-json'); const formatJsonBtn = document.getElementById('aog-js-format-json'); // Tab Elements const tabButtons = document.querySelectorAll('.aog-js-tab-button'); const tabPanes = document.querySelectorAll('.aog-js-tab-pane'); // UI State let isOpen = false; // Apply style updates function applyStyleUpdates() { // 1. Reduce size of section titles in API discovery and other tabs const sectionTitles = document.querySelectorAll('.aog-js-section-title'); sectionTitles.forEach(title => { title.style.fontSize = '16px'; title.style.fontWeight = '600'; }); // 2. Reduce size of section subtitles const sectionSubtitles = document.querySelectorAll('.aog-js-section-subtitle'); sectionSubtitles.forEach(subtitle => { subtitle.style.fontSize = '14px'; subtitle.style.fontWeight = '600'; }); // 3. Make all CTA buttons the same color (#ad24a6 - purple) const ctaButtons = document.querySelectorAll('.aog-js-primary-button'); ctaButtons.forEach(button => { button.style.backgroundColor = '#ad24a6'; button.style.color = 'white'; }); // 4. Remove the main JS API Explorer title from header const headerTitle = document.querySelector('.aog-js-explorer-header h2'); if (headerTitle) { headerTitle.style.display = 'none'; } // 5. Remove "Find All Collections" button if it exists const findAllBtn = document.getElementById('aog-js-find-all-btn'); if (findAllBtn) { findAllBtn.style.display = 'none'; } // 6. Update the collections tab description const collectionsDesc = document.querySelector('.aog-js-collections-description'); if (collectionsDesc) { collectionsDesc.textContent = 'Enter a collection name or ID to retrieve data. Use filters and sorting options below for more specific queries.'; } // 7. Update the API discovery tab description const apiDiscoveryDesc = document.querySelector('.aog-js-api-discovery-description'); if (apiDiscoveryDesc) { apiDiscoveryDesc.textContent = 'Discover available APIs on this site. To search for collections, go to the Collections tab and enter a collection name.'; } } // Check if running on a Duda site function checkDudaEnvironment() { const isDudaSite = typeof window.dmAPI !== 'undefined'; debug('Checking Duda environment', { isDudaSite }); apiState.isDudaSite = isDudaSite; if (isDudaSite) { coreApiAvailableEl.textContent = 'Available ✓'; coreApiAvailableEl.style.color = '#38a169'; } else { coreApiAvailableEl.textContent = 'Not detected'; coreApiAvailableEl.style.color = '#e53e3e'; // Show warning in API discovery tab showStatus('Warning: dmAPI not detected. Are you on a Duda site?', 'error', 'api-discovery-tab'); } return isDudaSite; } // Tab switching function switchTab(event) { const tabId = event.target.getAttribute('data-tab'); debug('Tab switch to:', tabId); // Update active tab button tabButtons.forEach(button => { button.classList.remove('active'); }); event.target.classList.add('active'); // Update active tab pane tabPanes.forEach(pane => { pane.classList.remove('active'); }); document.getElementById(`${tabId}-tab`).classList.add('active'); } // Toggle panel function togglePanel() { debug('Toggle panel called, current state:', isOpen); if (isOpen) { explorerPanel.style.display = 'none'; } else { explorerPanel.style.display = 'flex'; // Check if running on a Duda site checkDudaEnvironment(); // Apply style updates applyStyleUpdates(); } isOpen = !isOpen; } // Close panel function closePanel() { debug('Close panel called'); explorerPanel.style.display = 'none'; isOpen = false; } // Show status message in a specific tab function showStatus(message, type = 'info', tabId = null) { debug(`Status: ${message} (${type}) in tab: ${tabId || 'current'}`); let statusEl; // Determine which status element to use if (tabId === 'collections-tab') { statusEl = collectionsStatusEl; } else if (tabId === 'store-tab') { statusEl = storeStatusEl; } else if (tabId === 'content-library-tab') { statusEl = contentLibStatusEl; } else if (tabId === 'dynamic-pages-tab') { statusEl = dynamicPageStatusEl; } else if (tabId === 'api-discovery-tab') { // For discovery tab, show in all status elements const discoveryStatusEl = document.querySelector('#aog-js-discovery-results .aog-js-status'); if (discoveryStatusEl) { discoveryStatusEl.textContent = message; discoveryStatusEl.className = 'aog-js-status'; discoveryStatusEl.classList.add(type); discoveryStatusEl.style.display = 'block'; } return; } else { // Default to collections status statusEl = collectionsStatusEl; } statusEl.textContent = message; statusEl.className = 'aog-js-status'; // Reset classes statusEl.classList.add(type); statusEl.style.display = 'block'; // Auto-hide success messages after 5 seconds if (type === 'success') { setTimeout(() => { statusEl.style.display = 'none'; }, 5000); } } // Create collection item (for when collection is added to discovered collections) function createCollectionItem(id) { const item = document.createElement('div'); item.className = 'aog-js-collection-item'; const idEl = document.createElement('div'); idEl.className = 'aog-js-collection-id'; idEl.textContent = id; const loadEl = document.createElement('div'); loadEl.className = 'aog-js-collection-load'; loadEl.textContent = 'Load →'; item.appendChild(idEl); item.appendChild(loadEl); // Add click event to load this collection item.addEventListener('click', () => { collectionInput.value = id; findCollection(id); }); return item; } // Initialize all APIs async function initAllAPIs() { debug('Initializing all APIs...'); if (!apiState.isDudaSite) { showAllErrors('dmAPI not detected. Cannot initialize APIs.'); return; } // Show loading state dataEl.style.display = 'none'; loadingEl.style.display = 'flex'; try { // Initialize Collections API try { apiState.collectionsAPI = await window.dmAPI.loadCollectionsAPI(); collectionsApiStatusEl.textContent = 'Initialized ✓'; collectionsApiStatusEl.style.color = '#38a169'; } catch (error) { debug('Error initializing Collections API:', error); collectionsApiStatusEl.textContent = 'Failed: ' + error.message; collectionsApiStatusEl.style.color = '#e53e3e'; } // Initialize Content Library API try { apiState.contentLibraryAPI = await window.dmAPI.loadContentLibrary(); contentApiStatusEl.textContent = 'Initialized ✓'; contentApiStatusEl.style.color = '#38a169'; } catch (error) { debug('Error initializing Content Library API:', error); contentApiStatusEl.textContent = 'Failed: ' + error.message; contentApiStatusEl.style.color = '#e53e3e'; } // Initialize Dynamic Pages API try { apiState.dynamicPagesAPI = window.dmAPI.dynamicPageApi(); dynamicApiStatusEl.textContent = 'Initialized ✓'; dynamicApiStatusEl.style.color = '#38a169'; } catch (error) { debug('Error initializing Dynamic Pages API:', error); dynamicApiStatusEl.textContent = 'Failed: ' + error.message; dynamicApiStatusEl.style.color = '#e53e3e'; } // Check Members API const membersApiStatusEl = document.getElementById('aog-js-members-api-status'); try { if (typeof window.dmAPI.getLoggedInMember === 'function') { membersApiStatusEl.textContent = 'Available ✓'; membersApiStatusEl.style.color = '#38a169'; } else { membersApiStatusEl.textContent = 'Not Available'; membersApiStatusEl.style.color = '#dd6b20'; } } catch (error) { debug('Error checking Members API:', error); membersApiStatusEl.textContent = 'Failed: ' + error.message; membersApiStatusEl.style.color = '#e53e3e'; } // Show data section loadingEl.style.display = 'none'; dataEl.style.display = 'block'; showStatus('✅ APIs initialized successfully!', 'success', 'api-settings-tab'); } catch (error) { loadingEl.style.display = 'none'; dataEl.style.display = 'block'; showStatus(`❌ Error initializing APIs: ${error.message}`, 'error', 'api-settings-tab'); debug('Full initialization error:', error); } } // Show error in all status elements function showAllErrors(errorMsg) { showStatus(errorMsg, 'error', 'collections-tab'); showStatus(errorMsg, 'error', 'content-library-tab'); showStatus(errorMsg, 'error', 'store-tab'); showStatus(errorMsg, 'error', 'dynamic-pages-tab'); showStatus(errorMsg, 'error', 'api-discovery-tab'); showStatus(errorMsg, 'error', 'api-settings-tab'); } // Find collection with filters and also display in collections tab async function findCollection(collectionId) { if (!apiState.isDudaSite) { showStatus('Cannot find collection: dmAPI not detected', 'error', 'collections-tab'); return; } if (!apiState.collectionsAPI) { showStatus('Collections API not initialized. Click "Initialize All APIs" in Settings tab first.', 'error', 'collections-tab'); return; } if (!collectionId) { collectionId = collectionInput.value.trim(); } if (!collectionId) { showStatus('Please enter a collection name or ID', 'error', 'collections-tab'); return; } debug(`Finding collection: ${collectionId}`); showStatus(`Searching for collection: ${collectionId}...`, 'info', 'collections-tab'); try { // Build query with filters if provided let query = apiState.collectionsAPI.data(collectionId); // Add where clause if field is provided const filterField = filterFieldEl.value.trim(); const filterOperator = filterOperatorEl.value; const filterValue = filterValueEl.value.trim(); if (filterField && filterValue) { // Handle array operators (IN, NIN, BTWN) if (['IN', 'NIN', 'BTWN'].includes(filterOperator)) { const valueArray = filterValue.split(',').map(v => v.trim()); query = query.where(filterField, filterOperator, valueArray); } else { // Convert to number if it looks like one const value = !isNaN(filterValue) ? parseFloat(filterValue) : filterValue; query = query.where(filterField, filterOperator, value); } } // Add sort if field is provided const sortField = sortFieldEl.value.trim(); const sortDirection = sortDirectionEl.value; if (sortField) { query = query.orderBy(sortField, sortDirection); } // Add pagination const pageSize = parseInt(pageSizeEl.value) || 50; const pageNumber = parseInt(pageNumberEl.value) || 0; query = query.pageSize(pageSize).pageNumber(pageNumber); // Execute query const data = await query.get(); // Check if the response is actually an error if (isErrorResponse(data)) { showStatus(`❌ Error: ${data.message || 'Collection not found'}`, 'error', 'collections-tab'); showResults(data); return; } // Check if the response is valid and contains data if (data) { let itemCount = 0; let dataType = 'unknown'; // For array responses if (Array.isArray(data)) { itemCount = data.length; dataType = 'array'; if (data.length > 0) { showStatus(`✅ Successfully retrieved collection: ${collectionId} (${data.length} items)`, 'success', 'collections-tab'); } else { showStatus('⚠️ Collection found but contains no items.', 'info', 'collections-tab'); } } // For object responses like the one with values property else if (data.values && Array.isArray(data.values)) { itemCount = data.values.length; dataType = 'object with values array'; if (data.values.length > 0) { showStatus(`✅ Successfully retrieved collection: ${collectionId} (${data.values.length} items)`, 'success', 'collections-tab'); } else { showStatus('⚠️ Collection found but contains no items.', 'info', 'collections-tab'); } } // For other types of responses else if (typeof data === 'object' && Object.keys(data).length > 0) { itemCount = Object.keys(data).length; dataType = 'object'; showStatus(`✅ Successfully retrieved data for: ${collectionId}`, 'success', 'collections-tab'); } else { showStatus('⚠️ Response received but with unexpected format.', 'info', 'collections-tab'); } // Add the collection to discovered collections if not already there if (!apiState.discoveredCollections.includes(collectionId)) { apiState.discoveredCollections.push(collectionId); updateDiscoveredCollections(); } // Display in Raw JSON tab showResults(data); // Also display in collections tab displayCollectionData(collectionId, data, itemCount, dataType); } else { showStatus('⚠️ Received empty response.', 'error', 'collections-tab'); showResults({}); } } catch (error) { showStatus(`❌ Error finding collection: ${error.message}`, 'error', 'collections-tab'); showResults({ error: error.message }); // Try alternative methods if API call fails tryAlternativeMethods(collectionId); } } // Display collection data in the collections tab function displayCollectionData(collectionId, data, itemCount, dataType) { // Check if collection details section exists, create if not let collectionDetailsEl = document.getElementById('aog-js-collection-details'); if (!collectionDetailsEl) { collectionDetailsEl = document.createElement('div'); collectionDetailsEl.id = 'aog-js-collection-details'; collectionDetailsEl.className = 'aog-js-collection-details'; // Find where to insert it (after the discovered collections) const insertAfter = document.getElementById('aog-js-discovered-collections'); if (insertAfter && insertAfter.parentNode) { insertAfter.parentNode.insertBefore(collectionDetailsEl, insertAfter.nextSibling); } else { // Fallback - try to insert before the filter section const filterSection = document.querySelector('.aog-js-filter-section'); if (filterSection && filterSection.parentNode) { filterSection.parentNode.insertBefore(collectionDetailsEl, filterSection); } } } // Create the preview content collectionDetailsEl.innerHTML = ''; // Create the header const headerEl = document.createElement('div'); headerEl.className = 'aog-js-section-subtitle'; headerEl.textContent = 'Collection Preview'; collectionDetailsEl.appendChild(headerEl); // Create the details container const detailsContainer = document.createElement('div'); detailsContainer.className = 'aog-js-card'; const detailsBody = document.createElement('div'); detailsBody.className = 'aog-js-card-body'; // Create collection info const infoEl = document.createElement('div'); infoEl.className = 'aog-js-collection-info'; infoEl.innerHTML = ` <div class="aog-js-info-item"> <div class="aog-js-info-label">Collection Name:</div> <div class="aog-js-info-value">${collectionId}</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Items Count:</div> <div class="aog-js-info-value">${itemCount}</div> </div> <div class="aog-js-info-item"> <div class="aog-js-info-label">Data Type:</div> <div class="aog-js-info-value">${dataType}</div> </div> `; detailsBody.appendChild(infoEl); // Create data preview const previewTitle = document.createElement('div'); previewTitle.className = 'aog-js-section-subtitle'; previewTitle.textContent = 'Data Preview'; previewTitle.style.marginTop = '20px'; detailsBody.appendChild(previewTitle); // Format the data for display let previewContent = ''; try { // Show a truncated version (first item if array, or first few properties if object) if (Array.isArray(data) && data.length > 0) { previewContent = JSON.stringify(data[0], null, 2); if (data.length > 1) { previewContent += `\n\n// ... and ${data.length - 1} more items`; } } else if (data.values && Array.isArray(data.values) && data.values.length > 0) { previewContent = JSON.stringify(data.values[0], null, 2); if (data.values.length > 1) { previewContent += `\n\n// ... and ${data.values.length - 1} more items`; } } else { // For objects, limit the preview const fullJson = JSON.stringify(data, null, 2); previewContent = fullJson.length > 1000 ? fullJson.substring(0, 1000) + '...' : fullJson; } } catch (error) { previewContent = 'Error formatting data preview: ' + error.message; } const previewEl = document.createElement('pre'); previewEl.className = 'aog-js-collection-preview'; previewEl.textContent = previewContent; detailsBody.appendChild(previewEl); // Add view in raw JSON link const viewRawLink = document.createElement('button'); viewRawLink.className = 'aog-js-primary-button'; viewRawLink.style.marginTop = '15px'; viewRawLink.style.backgroundColor = '#ad24a6'; // Consistent purple color viewRawLink.textContent = 'View Full Data in Raw JSON Tab'; viewRawLink.addEventListener('click', () => { // Switch to raw JSON tab switchToTab('raw-json'); }); detailsBody.appendChild(viewRawLink); detailsContainer.appendChild(detailsBody); collectionDetailsEl.appendChild(detailsContainer); // Make sure the details are visible collectionDetailsEl.style.display = 'block'; } // Update discovered collections UI function updateDiscoveredCollections() { if (!collectionsListEl) return; collectionsListEl.innerHTML = ''; if (apiState.discoveredCollections.length === 0) { const emptyState = document.createElement('div'); emptyState.className = 'aog-js-empty-state'; emptyState.textContent = 'No discovered collections yet. Search for a collection by name in the field above.'; collectionsListEl.appendChild(emptyState); return; } // Update count if element exists if (discoveryCountEl) { discoveryCountEl.textContent = `${apiState.discoveredCollections.length} found`; } // Add each collection to the list apiState.discoveredCollections.forEach(id => { collectionsListEl.appendChild(createCollectionItem(id)); }); } // Try alternative methods to find collection async function tryAlternativeMethods(identifier) { debug('Trying alternative methods for: ' + identifier); showStatus('Trying alternative lookup methods...', 'info', 'collections-tab'); let found = false; // Method 1: Try to access using external data source if available if (typeof window.dm !== 'undefined' && typeof window.dm.getExternalDataSource === 'function') { try { const externalDataId = isNaN(identifier) ? identifier : parseInt(identifier); const externalData = await window.dm.getExternalDataSource(externalDataId); if (externalData && externalData.items) { showStatus(`✅ Found collection using dm.getExternalDataSource`, 'success', 'collections-tab'); showResults(externalData.items); // Also display in collections tab displayCollectionData(identifier, externalData.items, externalData.items.length, 'external data array'); found = true; // Add to discovered collections if not already there if (!apiState.discoveredCollections.includes(identifier)) { apiState.discoveredCollections.push(identifier); updateDiscoveredCollections(); } } } catch (error) { debug('Error using getExternalDataSource:', error.message); } } // Method 2: Try to look for global collections variable if (!found && (window.collectionsData || window.collections)) { const collections = window.collectionsData || window.collections; if (collections && collections[identifier]) { showStatus(`✅ Found collection in global variables`, 'success', 'collections-tab'); showResults(collections[identifier]); // Also display in collections tab displayCollectionData(identifier, collections[identifier], Array.isArray(collections[identifier]) ? collections[identifier].length : 1, 'global collection'); found = true; // Add to discovered collections if not already there if (!apiState.discoveredCollections.includes(identifier)) { apiState.discoveredCollections.push(identifier); updateDiscoveredCollections(); } } } if (!found) { showStatus(`Could not find collection "${identifier}" using any method.`, 'error', 'collections-tab'); } } // Discover APIs - Modified to not search for collections async function discoverAPIs() { if (!apiState.isDudaSite) { updateApiSectionStatus('core', 'Error', 'dmAPI not detected'); updateApiSectionStatus('collections', 'Error', 'dmAPI not detected'); updateApiSectionStatus('contentLibrary', 'Error', 'dmAPI not detected'); updateApiSectionStatus('store', 'Error', 'dmAPI not detected'); updateApiSectionStatus('dynamicPages', 'Error', 'dmAPI not detected'); updateApiSectionStatus('members', 'Error', 'dmAPI not detected'); updateApiSectionStatus('sitemap', 'Error', 'dmAPI not detected'); return; } debug('Discovering APIs...'); showStatus('Scanning site for available APIs...', 'info', 'api-discovery-tab'); // Initialize APIs if needed if (!apiState.collectionsAPI || !apiState.contentLibraryAPI || !apiState.dynamicPagesAPI) { await initAllAPIs(); } // Check core API methods discoverCoreAPI(); // Check if collections API is available (but don't search for collections) checkCollectionsAPIAvailability(); // Check content library discoverContentLibrary(); // Check store API discoverStoreAPI(); // Check dynamic pages discoverDynamicPages(); // Check members API discoverMembersAPI(); // Check sitemap discoverSitemap(); } // Check if Collections API is available without searching for collections async function checkCollectionsAPIAvailability() { updateApiSectionStatus('collections', 'Checking...'); try { if (!apiState.collectionsAPI) { try { apiState.collectionsAPI = await window.dmAPI.loadCollectionsAPI(); } catch (error) { updateApiSectionStatus('collections', 'Not Available', error.message); return; } } // If we get here, the API is available updateApiSectionStatus('collections', 'Available', 'Use Collections tab to search for collections'); // Display guidance in the results section const resultsEl = document.querySelector('.aog-js-api-section[data-section="collections"] .aog-js-api-section-results'); if (resultsEl) { resultsEl.innerHTML = '<div class="aog-js-guidance">Collections API is available. Go to the Collections tab to search for specific collections by name.</div>'; } } catch (error) { updateApiSectionStatus('collections', 'Error', error.message); } } // Find store catalog or categories async function findStoreData(type) { if (!apiState.isDudaSite) { showStatus('Cannot access store data: dmAPI not detected', 'error', 'store-tab'); return; } if (!apiState.collectionsAPI) { showStatus('Collections API not initialized. Click "Initialize All APIs" in Settings tab first.', 'error', 'store-tab'); return; } const storeType = type === 'catalog' ? 'catalog_product' : 'catalog_category'; debug(`Finding store ${type}`); showStatus(`Searching for store ${type}...`, 'info', 'store-tab'); try { // Build query with filters if provided let query = apiState.collectionsAPI.storeData(storeType); // Add where clause if field is provided const filterField = storeFilterFieldEl.value.trim(); const filterOperator = storeFilterOperatorEl.value; const filterValue = storeFilterValueEl.value.trim(); if (filterField && filterValue) { // Handle array operators (IN, NIN, BTWN) if (['IN', 'NIN', 'BTWN'].includes(filterOperator)) { const valueArray = filterValue.split(',').map(v => v.trim()); query = query.where(filterField, filterOperator, valueArray); } else { // Convert to number if it looks like one const value = !isNaN(filterValue) ? parseFloat(filterValue) : filterValue; query = query.where(filterField, filterOperator, value); } } // Add sort if field is provided const sortField = storeSortFieldEl.value.trim(); const sortDirection = storeSortDirectionEl.value; if (sortField) { query = query.orderBy(sortField, sortDirection); } // Execute query const data = await query.get(); // Check response if (isErrorResponse(data)) { showStatus(`❌ Error: ${data.message || 'Store data not found'}`, 'error', 'store-tab'); showResults(data); return; } if (data) { if (Array.isArray(data)) { if (data.length > 0) { showStatus(`✅ Successfully retrieved store ${type}: (${data.length} items)`, 'success', 'store-tab'); } else { showStatus(`⚠️ Store ${type} found but contains no items.`, 'info', 'store-tab'); } } else if (typeof data === 'object' && Object.keys(data).length > 0) { showStatus(`✅ Successfully retrieved store ${type} data`, 'success', 'store-tab'); } else { showStatus('⚠️ Response received but with unexpected format.', 'info', 'store-tab'); } showResults(data); } else { showStatus('⚠️ Received empty response.', 'error', 'store-tab'); showResults({}); } } catch (error) { showStatus(`❌ Error finding store ${type}: ${error.message}`, 'error', 'store-tab'); showResults({ error: error.message }); } } // Load content library async function loadContentLibrary() { if (!apiState.isDudaSite) { showStatus('Cannot access content library: dmAPI not detected', 'error', 'content-library-tab'); return; } if (!apiState.contentLibraryAPI) { try { showStatus('Initializing Content Library API...', 'info', 'content-library-tab'); apiState.contentLibraryAPI = await window.dmAPI.loadContentLibrary(); } catch (error) { showStatus(`❌ Error initializing Content Library API: ${error.message}`, 'error', 'content-library-tab'); return; } } debug('Loading content library'); try { const data = apiState.contentLibraryAPI; if (!data) { showStatus('❌ Content Library returned no data', 'error', 'content-library-tab'); return; } // Display the data in each section displayContentLibraryData(data); showStatus('✅ Successfully loaded Content Library data', 'success', 'content-library-tab'); showResults(data); } catch (error) { showStatus(`❌ Error loading Content Library: ${error.message}`, 'error', 'content-library-tab'); showResults({ error: error.message }); } } // Display content library data in sections function displayContentLibraryData(data) { // Helper to create a simple key-value display function createDataDisplay(obj, depth = 0) { if (!obj || typeof obj !== 'object') { return document.createTextNode(obj === null ? 'null' : obj === undefined ? 'undefined' : String(obj)); } const container = document.createElement('div'); container.style.paddingLeft = depth > 0 ? '15px' : '0'; // Handle arrays if (Array.isArray(obj)) { if (obj.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '(empty array)'; emptyMsg.style.fontStyle = 'italic'; emptyMsg.style.color = '#888'; container.appendChild(emptyMsg); } else { obj.forEach((item, index) => { const itemContainer = document.createElement('div'); itemContainer.style.marginBottom = '8px'; const indexEl = document.createElement('div'); indexEl.textContent = `[${index}]:`; indexEl.style.fontWeight = 'bold'; itemContainer.appendChild(indexEl); itemContainer.appendChild(createDataDisplay(item, depth + 1)); container.appendChild(itemContainer); }); } return container; } // Handle objects const keys = Object.keys(obj); if (keys.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.textContent = '(empty object)'; emptyMsg.style.fontStyle = 'italic'; emptyMsg.style.color = '#888'; container.appendChild(emptyMsg); } else { keys.forEach(key => { const row = document.createElement('div'); row.style.marginBottom = '8px'; const keyEl = document.createElement('div'); keyEl.textContent = `${key}:`; keyEl.style.fontWeight = 'bold'; row.appendChild(keyEl); const valueContainer = document.createElement('div'); valueContainer.style.marginLeft = '15px'; valueContainer.appendChild(createDataDisplay(obj[key], depth + 1)); row.appendChild(valueContainer); container.appendChild(row); }); } return container; } // Update each section document.querySelectorAll('.aog-js-content-section').forEach(section => { const sectionName = section.getAttribute('data-section'); const bodyEl = section.querySelector('.aog-js-content-section-body'); // Clear previous content bodyEl.innerHTML = ''; // Add new content if (data && data[sectionName]) { bodyEl.appendChild(createDataDisplay(data[sectionName])); } else { const notFoundEl = document.createElement('div'); notFoundEl.textContent = 'No data found for this section'; notFoundEl.style.fontStyle = 'italic'; notFoundEl.style.color = '#888'; notFoundEl.style.padding = '10px'; bodyEl.appendChild(notFoundEl); } }); } // Toggle content section function toggleContentSection(event) { const section = event.currentTarget.closest('.aog-js-content-section'); section.classList.toggle('expanded'); } // Check if current page is a dynamic page async function checkDynamicPage() { if (!apiState.isDudaSite) { showStatus('Cannot check dynamic page: dmAPI not detected', 'error', 'dynamic-pages-tab'); return; } try { if (!apiState.dynamicPagesAPI) { apiState.dynamicPagesAPI = window.dmAPI.dynamicPageApi(); } const isDynamic = apiState.dynamicPagesAPI.isDynamicPage(); isDynamicEl.textContent = isDynamic ? 'Yes ✓' : 'No'; isDynamicEl.style.color = isDynamic ? '#38a169' : '#666'; if (isDynamic) { showStatus('✅ This is a dynamic page. Loading page data...', 'success', 'dynamic-pages-tab'); try { const pageData = await apiState.dynamicPagesAPI.pageData(); showStatus('✅ Successfully loaded dynamic page data', 'success', 'dynamic-pages-tab'); showResults(pageData); // Display dynamic page info const infoEl = document.getElementById('aog-js-dynamic-page-info'); // Clear existing items except the first one const items = infoEl.querySelectorAll('.aog-js-info-item'); for (let i = 1; i < items.length; i++) { items[i].remove(); } // Add new info items if (pageData) { for (const key in pageData) { if (key !== 'data') { // Skip the data property, show it in raw JSON instead const item = document.createElement('div'); item.className = 'aog-js-info-item'; const label = document.createElement('div'); label.className = 'aog-js-info-label'; label.textContent = formatLabel(key) + ':'; const value = document.createElement('div'); value.className = 'aog-js-info-value'; value.textContent = typeof pageData[key] === 'object' ? JSON.stringify(pageData[key]).substring(0, 50) + '...' : pageData[key]; item.appendChild(label); item.appendChild(value); infoEl.appendChild(item); } } } } catch (error) { showStatus(`❌ Error loading dynamic page data: ${error.message}`, 'error', 'dynamic-pages-tab'); showResults({ error: error.message }); } } else { showStatus('❌ This is not a dynamic page', 'error', 'dynamic-pages-tab'); } } catch (error) { showStatus(`❌ Error checking dynamic page: ${error.message}`, 'error', 'dynamic-pages-tab'); isDynamicEl.textContent = 'Error'; isDynamicEl.style.color = '#e53e3e'; } } // Format label for display function formatLabel(str) { return str .replace(/_/g, ' ') .replace(/\w\S*/g, txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); } // Call core API method async function callCoreMethod(methodName) { if (!apiState.isDudaSite) { methodResultEl.textContent = 'Error: dmAPI not detected'; return; } debug(`Calling core method: ${methodName}`); try { let result; // Handle async methods differently if (methodName === 'getNavItemsAsync') { result = await window.dmAPI[methodName](); // Special handling for navigation data to make it more readable if (result) { methodResultEl.innerHTML = ''; // Create a formatted navigation display const navContainer = document.createElement('div'); navContainer.className = 'aog-js-nav-display'; function addNavItem(item, level = 0) { const itemEl = document.createElement('div'); itemEl.className = 'aog-js-nav-item'; itemEl.style.paddingLeft = `${level * 20}px`; const titleEl = document.createElement('span'); titleEl.className = 'aog-js-nav-title'; titleEl.textContent = item.title; const pathEl = document.createElement('span'); pathEl.className = 'aog-js-nav-path'; pathEl.textContent = ` (${item.path})`; const visibleEl = document.createElement('span'); visibleEl.className = 'aog-js-nav-visible'; visibleEl.textContent = item.visible ? ' - Visible' : ' - Hidden'; visibleEl.style.color = item.visible ? '#38a169' : '#e53e3e'; itemEl.appendChild(titleEl); itemEl.appendChild(pathEl); itemEl.appendChild(visibleEl); navContainer.appendChild(itemEl); // Add subnav items if any if (item.subNav && item.subNav.length > 0) { item.subNav.forEach(subItem => { addNavItem(subItem, level + 1); }); } } // Add heading const headingEl = document.createElement('div'); headingEl.className = 'aog-js-nav-heading'; headingEl.textContent = 'Navigation Structure:'; navContainer.appendChild(headingEl); // Process all nav items result.forEach(item => { addNavItem(item); }); methodResultEl.appendChild(navContainer); // Add link to raw JSON const rawLink = document.createElement('a'); rawLink.href = '#'; rawLink.textContent = 'Show raw JSON'; rawLink.className = 'aog-js-show-raw'; rawLink.addEventListener('click', (e) => { e.preventDefault(); methodResultEl.textContent = JSON.stringify(result, null, 2); }); const rawLinkContainer = document.createElement('div'); rawLinkContainer.className = 'aog-js-raw-link-container'; rawLinkContainer.appendChild(rawLink); methodResultEl.appendChild(rawLinkContainer); } else { methodResultEl.textContent = 'No navigation data returned'; } } else { // Call the method directly for non-async methods result = window.dmAPI[methodName](); // Format and display the result methodResultEl.textContent = typeof result === 'object' ? JSON.stringify(result, null, 2) : result; } // Also show in Raw JSON tab showResults(result); } catch (error) { methodResultEl.textContent = `Error: ${error.message}`; } } // Check if response contains an error message function isErrorResponse(data) { // Check for common error patterns in responses if (data && typeof data === 'object') { // Check for error message property if (data.message && typeof data.message === 'string') { const errorKeywords = ['error', 'no collection', 'not found', 'invalid', 'failed']; return errorKeywords.some(keyword => data.message.toLowerCase().includes(keyword)); } // Check for error property if (data.error || data.errorMessage || data.status === 'error') { return true; } } return false; } // Show results in raw JSON tab function showResults(data) { apiState.currentData = data; // Format and display in raw JSON tab if (rawJsonEl) { try { const formattedJson = JSON.stringify(data, null, 2); rawJsonEl.textContent = formattedJson; } catch (error) { rawJsonEl.textContent = 'Error formatting JSON data'; } } } // Switch to specific tab function switchToTab(tabId) { // Find the tab button const tabButton = document.querySelector(`.aog-js-tab-button[data-tab="${tabId}"]`); if (tabButton) { // Create a mock event object const event = { target: tabButton }; switchTab(event); } } // Copy JSON to clipboard function copyJsonToClipboard() { try { const text = rawJsonEl.textContent; navigator.clipboard.writeText(text).then(function() { showCopiedNotification(); }).catch(function(err) { debug('Clipboard API error:', err); // Fallback method for older browsers copyToClipboardFallback(text); }); } catch (err) { debug('Failed to copy:', err); } } // Format JSON function formatJson() { try { if (!apiState.currentData) { return; } const formatted = JSON.stringify(apiState.currentData, null, 2); rawJsonEl.textContent = formatted; } catch (error) { debug('Error formatting JSON:', error); } } // Fallback clipboard method for browsers without clipboard API function copyToClipboardFallback(text) { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const successful = document.execCommand('copy'); if (successful) { showCopiedNotification(); } else { debug('Failed to copy text with execCommand'); } } catch (err) { debug('Error during execCommand copy:', err); } document.body.removeChild(textArea); } // Show copied notification function showCopiedNotification() { // Create notification if it doesn't exist let notification = document.querySelector('.aog-js-copied-notification'); if (!notification) { notification = document.createElement('div'); notification.className = 'aog-js-copied-notification'; notification.textContent = 'JSON copied to clipboard'; document.body.appendChild(notification); } // Show notification notification.classList.add('show'); // Hide notification after delay setTimeout(() => { notification.classList.remove('show'); }, 2000); } // Toggle debug mode function toggleDebugMode() { DEBUG = debugToggle.checked; debug('Debug mode toggled:', DEBUG); } // Discover Members API async function discoverMembersAPI() { updateApiSectionStatus('members', 'Checking...'); try { // Check if getLoggedInMember method exists if (typeof window.dmAPI.getLoggedInMember === 'function') { try { // Try to call the method await window.dmAPI.getLoggedInMember(); updateApiSectionStatus('members', 'Available', 'Members API active'); } catch (error) { // If we get a specific error about not being logged in, API is still available if (error && error.message && error.message.includes('not logged in')) { updateApiSectionStatus('members', 'Available', 'No member logged in'); } else { updateApiSectionStatus('members', 'Not Available', error.message); } } } else { updateApiSectionStatus('members', 'Not Available', 'Members API not found'); } } catch (error) { updateApiSectionStatus('members', 'Error', error.message); } } // Check for logged-in member async function checkLoggedInMember() { if (!apiState.isDudaSite) { showStatus('Cannot check member: dmAPI not detected', 'error', 'members-tab'); return; } const memberStatusEl = document.getElementById('aog-js-member-status'); const isLoggedInEl = document.getElementById('aog-js-is-logged-in'); const memberInfoEl = document.getElementById('aog-js-member-info'); try { showStatus('Checking for logged-in member...', 'info', 'members-tab'); if (typeof window.dmAPI.getLoggedInMember !== 'function') { showStatus('❌ Members API not available on this site', 'error', 'members-tab'); isLoggedInEl.textContent = 'API Not Available'; isLoggedInEl.style.color = '#e53e3e'; return; } try { const memberData = await window.dmAPI.getLoggedInMember(); // Check if this is a placeholder/default user or a real logged-in user const isPlaceholder = !memberData || (memberData.firstName === 'John' && memberData.lastName === 'Doe') || !memberData.uuid || Object.keys(memberData).length < 3; if (isPlaceholder) { isLoggedInEl.textContent = 'Not Logged In (Default Data)'; isLoggedInEl.style.color = '#dd6b20'; showStatus('ℹ️ No real member is logged in (placeholder data detected)', 'info', 'members-tab'); } else { // Real user is logged in isLoggedInEl.textContent = 'Logged In ✓'; isLoggedInEl.style.color = '#38a169'; showStatus('✅ Member is logged in, retrieved member data', 'success', 'members-tab'); } showResults(memberData); // Display member info // Clear existing items except the first one const items = memberInfoEl.querySelectorAll('.aog-js-info-item'); for (let i = 1; i < items.length; i++) { items[i].remove(); } // Add new info items if (memberData) { // Add a note if we think it's placeholder data if (isPlaceholder) { const placeholderNote = document.createElement('div'); placeholderNote.className = 'aog-js-placeholder-note'; placeholderNote.textContent = 'Note: This appears to be placeholder data, not a real logged-in user.'; placeholderNote.style.color = '#dd6b20'; placeholderNote.style.fontStyle = 'italic'; placeholderNote.style.marginBottom = '15px'; placeholderNote.style.padding = '8px'; placeholderNote.style.backgroundColor = '#fff5f5'; placeholderNote.style.borderRadius = '4px'; memberInfoEl.appendChild(placeholderNote); } for (const key in memberData) { const item = document.createElement('div'); item.className = 'aog-js-info-item'; const label = document.createElement('div'); label.className = 'aog-js-info-label'; label.textContent = formatLabel(key) + ':'; const value = document.createElement('div'); value.className = 'aog-js-info-value'; value.textContent = typeof memberData[key] === 'object' ? JSON.stringify(memberData[key]).substring(0, 50) + '...' : memberData[key]; item.appendChild(label); item.appendChild(value); memberInfoEl.appendChild(item); } } } catch (error) { if (error && error.message && error.message.includes('not logged in')) { isLoggedInEl.textContent = 'Not Logged In'; isLoggedInEl.style.color = '#dd6b20'; showStatus('ℹ️ No member is currently logged in', 'info', 'members-tab'); } else { isLoggedInEl.textContent = 'Error'; isLoggedInEl.style.color = '#e53e3e'; showStatus(`❌ Error checking member: ${error.message}`, 'error', 'members-tab'); } } } catch (error) { showStatus(`❌ Error: ${error.message}`, 'error', 'members-tab'); } } // Discover Sitemap async function discoverSitemap() { updateApiSectionStatus('sitemap', 'Checking...'); try { // Try to fetch the sitemap.xml file const domain = window.location.hostname; const sitemapUrl = `https://${domain}/sitemap.xml`; try { const response = await fetch(sitemapUrl, { method: 'HEAD' }); if (response.ok) { updateApiSectionStatus('sitemap', 'Available', 'Sitemap.xml found'); } else { updateApiSectionStatus('sitemap', 'Not Available', 'Sitemap.xml not found'); } } catch (error) { updateApiSectionStatus('sitemap', 'Not Available', 'Cannot access sitemap.xml'); } } catch (error) { updateApiSectionStatus('sitemap', 'Error', error.message); } } // Fetch and parse sitemap async function fetchSitemap() { if (!apiState.isDudaSite) { showStatus('Cannot fetch sitemap: Site not detected', 'error', 'sitemap-tab'); return; } const sitemapStatusEl = document.getElementById('aog-js-sitemap-status'); const sitemapStatsEl = document.getElementById('aog-js-sitemap-stats'); const sitemapCountEl = document.getElementById('aog-js-sitemap-count'); const sitemapUpdatedEl = document.getElementById('aog-js-sitemap-updated'); const sitemapContainerEl = document.getElementById('aog-js-sitemap-container'); const sitemapUrlsEl = document.getElementById('aog-js-sitemap-urls'); try { showStatus('Fetching sitemap.xml...', 'info', 'sitemap-tab'); const domain = window.location.hostname; const sitemapUrl = `https://${domain}/sitemap.xml`; try { // Try to fetch the sitemap directly const response = await fetch(sitemapUrl); if (response.ok) { const text = await response.text(); // Parse the XML const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, 'text/xml'); const urls = xmlDoc.getElementsByTagName('url'); if (urls.length > 0) { // Build site URL list sitemapUrlsEl.innerHTML = ''; const urlList = document.createElement('ul'); urlList.className = 'aog-js-sitemap-url-list'; // Extract URLs const siteUrls = []; for (let i = 0; i < urls.length; i++) { const loc = urls[i].getElementsByTagName('loc')[0]; if (loc) { siteUrls.push({ url: loc.textContent, lastmod: urls[i].getElementsByTagName('lastmod')[0]?.textContent }); } } // Sort by URL siteUrls.sort((a, b) => a.url.localeCompare(b.url)); // Add to list siteUrls.forEach(item => { const li = document.createElement('li'); li.className = 'aog-js-sitemap-url-item'; const urlEl = document.createElement('a'); urlEl.href = item.url; urlEl.target = '_blank'; urlEl.textContent = item.url.replace(`https://${domain}`, ''); li.appendChild(urlEl); if (item.lastmod) { const lastmodEl = document.createElement('span'); lastmodEl.className = 'aog-js-sitemap-lastmod'; lastmodEl.textContent = ` (${new Date(item.lastmod).toLocaleDateString()})`; li.appendChild(lastmodEl); } urlList.appendChild(li); }); sitemapUrlsEl.appendChild(urlList); // Update stats sitemapCountEl.textContent = urls.length; sitemapUpdatedEl.textContent = new Date().toLocaleDateString(); // Show the sections sitemapStatsEl.style.display = 'block'; sitemapContainerEl.style.display = 'block'; showStatus(`✅ Successfully retrieved sitemap with ${urls.length} URLs`, 'success', 'sitemap-tab'); showResults({ sitemapUrl: sitemapUrl, urlCount: urls.length, urls: siteUrls }); } else { showStatus('⚠️ Sitemap found but contains no URLs', 'info', 'sitemap-tab'); } } else { showStatus(`❌ Could not access sitemap.xml: ${response.status} ${response.statusText}`, 'error', 'sitemap-tab'); } } catch (error) { // If direct fetch fails, try a proxy approach or CORS workaround showStatus(`❌ Error fetching sitemap: ${error.message}`, 'error', 'sitemap-tab'); showStatus('Note: Direct access to sitemap.xml may be restricted by CORS. Try viewing it directly in your browser.', 'info', 'sitemap-tab'); } } catch (error) { showStatus(`❌ Error: ${error.message}`, 'error', 'sitemap-tab'); } } // Update API section status in discovery tab function updateApiSectionStatus(section, status, details = '') { const sectionEl = document.querySelector(`.aog-js-api-section[data-section="${section}"]`); if (!sectionEl) return; const statusEl = sectionEl.querySelector('.aog-js-api-section-status'); if (!statusEl) return; // Set status text statusEl.textContent = status + (details ? ': ' + details : ''); // Set status color if (status === 'Available' || status === 'Active') { statusEl.style.color = '#38a169'; } else if (status === 'Not Available') { statusEl.style.color = '#dd6b20'; } else if (status === 'Error') { statusEl.style.color = '#e53e3e'; } else { statusEl.style.color = '#666'; } } // Discover core API methods function discoverCoreAPI() { updateApiSectionStatus('core', 'Checking...'); try { const coreMethodsAvailable = []; // List of core methods to check const coreMethods = [ 'getSiteName', 'getSiteExternalId', 'getSitePlanID', 'getCurrentDeviceType', 'getCurrentEnvironment', 'getNavItemsAsync' ]; // Check which methods are available coreMethods.forEach(method => { if (typeof window.dmAPI[method] === 'function') { coreMethodsAvailable.push(method); } }); if (coreMethodsAvailable.length > 0) { updateApiSectionStatus('core', 'Available', `${coreMethodsAvailable.length} methods found`); // Display available methods const resultsEl = document.querySelector('.aog-js-api-section[data-section="core"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; coreMethodsAvailable.forEach(method => { const methodEl = document.createElement('div'); methodEl.className = 'aog-js-discovered-method'; methodEl.textContent = method; resultsEl.appendChild(methodEl); }); } else { updateApiSectionStatus('core', 'Not Available', 'No core methods found'); } } catch (error) { updateApiSectionStatus('core', 'Error', error.message); } } // Discover content library async function discoverContentLibrary() { updateApiSectionStatus('contentLibrary', 'Checking...'); try { if (!apiState.contentLibraryAPI) { try { apiState.contentLibraryAPI = await window.dmAPI.loadContentLibrary(); } catch (error) { updateApiSectionStatus('contentLibrary', 'Not Available', error.message); return; } } if (apiState.contentLibraryAPI) { const sections = []; // Check which content library sections have data if (apiState.contentLibraryAPI.business_data) sections.push('business_data'); if (apiState.contentLibraryAPI.location_data) sections.push('location_data'); if (apiState.contentLibraryAPI.additional_locations) sections.push('additional_locations'); if (apiState.contentLibraryAPI.site_texts) sections.push('site_texts'); if (apiState.contentLibraryAPI.site_images) sections.push('site_images'); updateApiSectionStatus('contentLibrary', 'Available', `${sections.length} sections with data`); // Display available sections const resultsEl = document.querySelector('.aog-js-api-section[data-section="contentLibrary"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; sections.forEach(section => { const sectionEl = document.createElement('div'); sectionEl.className = 'aog-js-discovered-item'; sectionEl.textContent = formatLabel(section); resultsEl.appendChild(sectionEl); }); } else { updateApiSectionStatus('contentLibrary', 'Not Available', 'No content library data found'); } } catch (error) { updateApiSectionStatus('contentLibrary', 'Error', error.message); } } // Discover store API async function discoverStoreAPI() { updateApiSectionStatus('store', 'Checking...'); try { if (!apiState.collectionsAPI) { try { apiState.collectionsAPI = await window.dmAPI.loadCollectionsAPI(); } catch (error) { updateApiSectionStatus('store', 'Not Available', error.message); return; } } // Check if storeData method exists if (typeof apiState.collectionsAPI.storeData === 'function') { try { // Try to get a product from the catalog const product = await apiState.collectionsAPI.storeData('catalog_product').pageSize(1).get(); const hasProducts = Array.isArray(product) && product.length > 0; // Try to get a category const category = await apiState.collectionsAPI.storeData('catalog_category').pageSize(1).get(); const hasCategories = Array.isArray(category) && category.length > 0; if (hasProducts || hasCategories) { updateApiSectionStatus('store', 'Available', 'Store API active'); // Display store data info const resultsEl = document.querySelector('.aog-js-api-section[data-section="store"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; if (hasProducts) { const productEl = document.createElement('div'); productEl.className = 'aog-js-discovered-item'; productEl.textContent = 'Product Catalog Available'; resultsEl.appendChild(productEl); } if (hasCategories) { const categoryEl = document.createElement('div'); categoryEl.className = 'aog-js-discovered-item'; categoryEl.textContent = 'Product Categories Available'; resultsEl.appendChild(categoryEl); } } else { updateApiSectionStatus('store', 'Available', 'No products or categories found'); } } catch (error) { updateApiSectionStatus('store', 'Not Available', 'Store API error: ' + error.message); } } else { updateApiSectionStatus('store', 'Not Available', 'Store API method not found'); } } catch (error) { updateApiSectionStatus('store', 'Error', error.message); } } // Discover dynamic pages async function discoverDynamicPages() { updateApiSectionStatus('dynamicPages', 'Checking...'); try { if (!apiState.dynamicPagesAPI) { try { apiState.dynamicPagesAPI = window.dmAPI.dynamicPageApi(); } catch (error) { updateApiSectionStatus('dynamicPages', 'Not Available', error.message); return; } } const isDynamic = apiState.dynamicPagesAPI.isDynamicPage(); if (isDynamic) { const pageData = await apiState.dynamicPagesAPI.pageData(); updateApiSectionStatus('dynamicPages', 'Active', 'Current page is a dynamic page'); // Display dynamic page info const resultsEl = document.querySelector('.aog-js-api-section[data-section="dynamicPages"] .aog-js-api-section-results'); resultsEl.innerHTML = ''; if (pageData) { const keysEl = document.createElement('div'); keysEl.className = 'aog-js-discovered-item'; keysEl.textContent = `Available fields: ${Object.keys(pageData).join(', ')}`; resultsEl.appendChild(keysEl); } } else { updateApiSectionStatus('dynamicPages', 'Available', 'Current page is not a dynamic page'); } } catch (error) { updateApiSectionStatus('dynamicPages', 'Error', error.message); } } // Setup event listeners debug('Setting up event listeners'); // Main button toggle if (explorerButton) { explorerButton.addEventListener('click', togglePanel); } // Close button if (closeButton) { closeButton.addEventListener('click', closePanel); } // Initialize All APIs button if (initAllBtn) { initAllBtn.addEventListener('click', initAllAPIs); } // Content section toggles contentSectionElements.forEach(section => { const headerEl = section.querySelector('.aog-js-content-section-header'); if (headerEl) { headerEl.addEventListener('click', toggleContentSection); } }); // Find collection button if (findBtn) { findBtn.addEventListener('click', () => findCollection()); } // Collection input enter key if (collectionInput) { collectionInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { findCollection(); } }); } // Store buttons if (loadCatalogBtn) { loadCatalogBtn.addEventListener('click', () => findStoreData('catalog')); } if (loadCategoriesBtn) { loadCategoriesBtn.addEventListener('click', () => findStoreData('categories')); } // Content Library button if (loadContentLibraryBtn) { loadContentLibraryBtn.addEventListener('click', loadContentLibrary); } // Dynamic Pages button if (checkDynamicPageBtn) { checkDynamicPageBtn.addEventListener('click', checkDynamicPage); } // Members API button const checkMemberBtn = document.getElementById('aog-js-check-member'); if (checkMemberBtn) { checkMemberBtn.addEventListener('click', checkLoggedInMember); } // Sitemap button const fetchSitemapBtn = document.getElementById('aog-js-fetch-sitemap'); if (fetchSitemapBtn) { fetchSitemapBtn.addEventListener('click', fetchSitemap); } // Core method buttons methodButtons.forEach(button => { button.addEventListener('click', () => { const method = button.getAttribute('data-method'); if (method) { callCoreMethod(method); } }); }); // API Discovery button if (discoverAllBtn) { discoverAllBtn.addEventListener('click', discoverAPIs); } // Debug toggle if (debugToggle) { debugToggle.addEventListener('change', toggleDebugMode); } // Copy JSON button if (copyJsonBtn) { copyJsonBtn.addEventListener('click', copyJsonToClipboard); } // Format JSON button if (formatJsonBtn) { formatJsonBtn.addEventListener('click', formatJson); } // Tab switching tabButtons.forEach(button => { button.addEventListener('click', switchTab); }); // Show data section by default dataEl.style.display = 'block'; // Check if we're on a Duda site checkDudaEnvironment(); // Apply style updates applyStyleUpdates(); } // Initialize on load if (document.readyState === 'complete' || document.readyState === 'interactive') { // Document already loaded, initialize immediately initEnhancedJSAPIExplorer(); } else { // Wait for document to load document.addEventListener('DOMContentLoaded', initEnhancedJSAPIExplorer); } })(); </script> <!-- JAVASCRIPT SECTION END --> </div>
Once published, you'll see a little widget appear in the bottom left corner of your page.
Step 4: Extract Your Collection Data
This is the clever bit:
- Click on the JS API Explorer widget
- Hit "Discover Available APIs" – this initializes everything
- Go to the "Collections" tab
- Type in your collection name ("Team Members")
- Click "Find"
If you've published your collection properly, you'll see your data appear. Want the full JSON? Click "View Full Data" in the Raw JSON tab and copy the whole thing.
Step 5: The Claude Prompt That Makes Magic
Head over to Claude (if you're an Agency Genius member with our custom instructions installed, this will work even better).
Start your prompt with something like:
I want to create a modern, visually stunning team member grid component for my website. The site uses a purple and white color scheme (#ad24a6 as the primary color). I want it to have: - Smooth hover animations - Maybe some filtering alphabetically by first name - A layout that actually looks good on mobile - Something that feels fresh and modern
Then add this crucial bit:
I want this component to be powered by a Duda collection called [COLLECTION_NAME]. The widget must work in both the Duda editor and on published live sites. For the collection integration: - In the editor, use dmAPI.loadCollectionsAPI() - On live sites, check these locations in order: 1. window.$COLLECTION_DATA[' [COLLECTION_NAME] '] 2. window.site_data.collections (find by name) 3. window.runtime.collections[' [COLLECTION_NAME] '] 4. Script tags with data-collection-name=" [COLLECTION_NAME] " - Handle both array format and object with values array - Show "Loading..." while fetching, and meaningful error messages if collection not found - Wait for DOM ready and retry once if dmAPI isn't immediately available Here is the raw JSON for what the collection's output data will typically look like: [PASTE YOUR JSON FROM THE JS API EXPLORER HERE]
Hit enter and watch Claude create something beautiful.
Step 6: The "Wow" Moment
Copy the code Claude gives you, paste it into an HTML widget on your Duda site, publish, and see your collection data transformed into something special.
But Wait, There's More (The Creative Bit)
Here's where I love to push things further. AI has seen thousands of design patterns and can combine them in ways we might not think of.
So here's your challenge: Once Claude gives you that first design, follow up with:
That's really good, thank you! But I'd like you to give me 3-4 different alternative designs using the same color scheme. I want each design to have something unique – maybe something I haven't thought about. Surprise me.
I guarantee you'll get at least one design that makes you think "Oh, I like that approach!"
The Bottom Line
Duda collections are powerful, and now they can be beautiful too. The combination of the JS API Explorer and Claude means you can create collection-powered components that look like they were custom-coded by a developer.
We've all built functional things that we wished looked better. Now we don't have to compromise. We can have both the power of dynamic data and designs that make us proud.
So let's raise the bar together. Let's make collections that make other Duda users ask "How did you do that?"
Now go create something amazing. And when someone asks how you did it, send them this blog.
Want to level up your Duda game even more? Join Agency Genius and get access to our custom Claude instructions, exclusive widgets, and a community of people who love pushing the boundaries of what's possible with Duda.
Join For Free
Become a member today on our free tier and get instant access to 5 custom components.