Render interactive catalog resources in an in-app HTML viewer - #51
Render interactive catalog resources in an in-app HTML viewer#51soyalejolopez wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces an interactive Resource Viewer Modal to index.html, allowing users to preview resources directly within an iframe. It includes the necessary CSS styling, HTML structure, and JavaScript logic to open and close the viewer, as well as handling the 'Escape' key and backdrop clicks. The review feedback suggests enhancing security by adding a sandbox attribute to the iframe, and improving accessibility by managing focus states and handling keydown events within same-origin iframe contexts.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| <button class="viewer-close" id="viewerClose" type="button" aria-label="Close viewer">×</button> | ||
| </div> | ||
| </div> | ||
| <iframe class="viewer-iframe" id="viewerFrame" title="Resource preview" loading="lazy" referrerpolicy="no-referrer"></iframe> |
There was a problem hiding this comment.
To follow security best practices and enforce a strong security boundary, consider adding a sandbox attribute to the <iframe>. Since the embedded interactive resources are same-origin and require scripts, using allow-scripts allow-same-origin allow-popups allow-forms will ensure they function correctly while preventing them from navigating the top-level window (allow-top-navigation is omitted).
<iframe class="viewer-iframe" id="viewerFrame" title="Resource preview" loading="lazy" referrerpolicy="no-referrer" sandbox="allow-scripts allow-same-origin allow-popups allow-forms"></iframe>| function openViewer(name, url) { | ||
| const overlay = document.getElementById('viewerOverlay'); | ||
| const frame = document.getElementById('viewerFrame'); | ||
| document.getElementById('viewerTitle').textContent = name; | ||
| document.getElementById('viewerOpenTab').href = url; | ||
| frame.src = url; | ||
| overlay.classList.add('open'); | ||
| document.body.style.overflow = 'hidden'; | ||
| if (typeof window.clarity !== 'undefined') { window.clarity('event', 'resource_preview'); } | ||
| } | ||
|
|
||
| function closeViewer() { | ||
| const overlay = document.getElementById('viewerOverlay'); | ||
| const frame = document.getElementById('viewerFrame'); | ||
| overlay.classList.remove('open'); | ||
| frame.src = 'about:blank'; | ||
| document.body.style.overflow = ''; | ||
| } |
There was a problem hiding this comment.
To improve accessibility (A11y) and user experience, we should manage focus when opening and closing the modal. Specifically:
- Store the currently focused element before opening the modal, and restore focus to it when the modal is closed.
- Set focus to the close button when the modal opens so keyboard users can easily dismiss it.
- Bind an Escape key listener inside the same-origin iframe's document, as keydown events inside an iframe do not bubble up to the parent document.
let activeElementBeforeModal = null;
function openViewer(name, url) {
activeElementBeforeModal = document.activeElement;
const overlay = document.getElementById('viewerOverlay');
const frame = document.getElementById('viewerFrame');
document.getElementById('viewerTitle').textContent = name;
document.getElementById('viewerOpenTab').href = url;
frame.src = url;
// Enable Escape key to close the viewer even when focus is inside the same-origin iframe
frame.onload = () => {
try {
frame.contentDocument.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeViewer();
});
} catch (err) {
// Ignore cross-origin or loading errors
}
};
overlay.classList.add('open');
document.body.style.overflow = 'hidden';
document.getElementById('viewerClose').focus();
if (typeof window.clarity !== 'undefined') { window.clarity('event', 'resource_preview'); }
}
function closeViewer() {
const overlay = document.getElementById('viewerOverlay');
const frame = document.getElementById('viewerFrame');
overlay.classList.remove('open');
frame.src = 'about:blank';
document.body.style.overflow = '';
if (activeElementBeforeModal) {
activeElementBeforeModal.focus();
}
}Interactive resources (Copilot Agents Guide, Agents Cost Calculator) now render their HTML page in an in-app modal (window-in-window) with a header that links to the full page in a new tab, instead of only linking to GitHub. The embed path is derived from each resource's source (README.md -> index.html), so no catalog.json schema changes are required. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dad50700-e239-43cc-8994-b475682137c4
4c27395 to
dbf1bb8
Compare
Interactive catalog resources (Copilot Agents Guide, Agents Cost Calculator) now render their HTML page inside the app via a View button that opens a window-in-window modal with an Open-in-new-tab header link, instead of only linking to GitHub. Only index.html changed.