Skip to content

Conversation

@EthanThePhoenix38
Copy link
Member

No description provided.

Adds a Portfolio navigation item in the Site section of the backend
dashboard, allowing easy access to the portfolio page via iframe.

https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Create docs.html with markdown parsing via GitHub API
- Fix backend: bypass login (Supabase not configured), link to docs.html
- Fix portfolio: repo count shows only non-fork repos
- Fix footer links: point to ThePhoenixAgency.github.io

https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
- Create viewer.html to display GitHub repos with template styling
- Update portfolio to open project links in viewer instead of new tabs
- Add section anchors sidebar in docs.html with sticky navigation
- Generate anchor IDs from markdown headings

https://claude.ai/code/session_0138bAjho1fWwiRZju3nJFJ3
@EthanThePhoenix38 EthanThePhoenix38 marked this pull request as ready for review February 9, 2026 11:02
Copilot AI review requested due to automatic review settings February 9, 2026 11:02
@EthanThePhoenix38 EthanThePhoenix38 merged commit 744a043 into main Feb 9, 2026
10 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new in-site pages to view GitHub repository content and project documentation, and wires portfolio/backend navigation to these pages.

Changes:

  • Added viewer.html to fetch GitHub repo metadata + README and render it in a styled “viewer” page.
  • Added docs.html to browse markdown files from docs/ with a sidebar and generated section anchors.
  • Updated portfolio.html and backend.html navigation/links to point to the new viewer/docs pages; adjusted some footer links.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
viewer.html New repo viewer page that fetches GitHub repo info + README and renders it.
docs.html New documentation browser that fetches markdown docs from GitHub and renders them with anchors.
portfolio.html Routes project links to viewer.html and adjusts GitHub/org stats calculation and footer link.
backend.html Adds Portfolio/Documentation navigation entries and changes session/login behavior.
about.html Updates footer “ThePhoenixAgency” link to external website.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +172 to +173
<a href="javascript:history.back()">Back</a>
<span class="viewer-title" id="viewerTitle">Loading...</span>
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a javascript: URL in an href (Back link) is discouraged and can be blocked by CSP / security scanners. Prefer a normal link target (e.g., portfolio.html) and/or attach a click handler that calls history.back() with a safe fallback when there is no history.

Copilot uses AI. Check for mistakes.
Comment on lines +349 to +350
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');

Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown links are emitted as <a href="$2" …> from raw markdown without attribute escaping or URL scheme validation. A malicious doc can inject quotes/attributes or javascript: URLs, resulting in XSS when docsContent.innerHTML is set. Use marked + DOMPurify (already used in readme-viewer.html) or implement strict escaping + allowlist URL schemes (http/https) before generating anchors.

Suggested change
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
function escapeHtml(str) {
return str.replace(/[&<>"']/g, function (ch) {
switch (ch) {
case '&': return '&amp;';
case '<': return '&lt;';
case '>': return '&gt;';
case '"': return '&quot;';
case '\'': return '&#39;';
default: return ch;
}
});
}
function sanitizeUrl(url) {
if (!url) return null;
const trimmed = url.trim();
// Allow same-page anchors
if (trimmed.startsWith('#')) {
return trimmed;
}
try {
const parsed = new URL(trimmed, window.location.origin);
const protocol = parsed.protocol.toLowerCase();
if (protocol === 'http:' || protocol === 'https:') {
return parsed.href;
}
} catch (e) {
// Invalid URL, fall through to null
}
return null;
}
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
const safeUrl = sanitizeUrl(href);
const safeText = escapeHtml(text);
if (!safeUrl) {
// If URL is unsafe or invalid, render just the escaped text
return safeText;
}
return `<a href="${safeUrl}" target="_blank" rel="noopener">${safeText}</a>`;
});

Copilot uses AI. Check for mistakes.
Comment on lines +399 to +406
try {
// Try GitHub API first for rendered markdown
const apiUrl = `https://api.github.com/repos/${DOCS_CONFIG.owner}/${DOCS_CONFIG.repo}/contents/${DOCS_CONFIG.docsPath}/${filename}`;

const response = await fetch(apiUrl, {
headers: {
'Accept': 'application/vnd.github.v3.raw'
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment says “Try GitHub API first for rendered markdown”, but the request uses Accept: application/vnd.github.v3.raw which returns raw markdown. Update the comment to match behavior (raw fetch + local render) to avoid confusion when maintaining this code.

Copilot uses AI. Check for mistakes.
Comment on lines +408 to 412
// Check if already logged in - bypass login for now
function checkSession() {
const session = localStorage.getItem('supabase_session');
if (session) {
showDashboard();
}
// Always show dashboard (Supabase not configured)
showDashboard();
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkSession() now always calls showDashboard(), bypassing the login overlay entirely. This makes backend.html effectively unauthenticated even if/when Supabase is later configured, and can expose admin/dashboard UI publicly. Gate this behind an explicit dev flag, or keep the session check and require login when no valid session exists.

Copilot uses AI. Check for mistakes.

<div class="social-links">
<a href="https://github.com/ThePhoenixAgency" target="_blank" class="social-link">
<a href="viewer.html?url=https://github.com/ThePhoenixAgency" class="social-link">
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GitHub social link now routes to viewer.html with an org/user URL (https://github.com/ThePhoenixAgency), but viewer.html only supports GitHub repository URLs (owner/repo) and will show “Unsupported URL”. Either keep this link pointing directly to GitHub (as before), or extend viewer.html to support org/user pages (or pass a supported repo URL).

Suggested change
<a href="viewer.html?url=https://github.com/ThePhoenixAgency" class="social-link">
<a href="https://github.com/ThePhoenixAgency" target="_blank" class="social-link">

Copilot uses AI. Check for mistakes.
Comment on lines +416 to 435
// Fetch repos first (single API call for all data)
const reposResponse = await fetch(`https://api.github.com/orgs/${PORTFOLIO_CONFIG.githubOrg}/repos?per_page=100`);
if (!reposResponse.ok) throw new Error('Failed to fetch repos');

let repos = await reposResponse.json();
if (!Array.isArray(repos)) throw new Error('Invalid response');

// Calculate total stars
// Filter: hide forks FIRST before counting
if (PORTFOLIO_CONFIG.hideForks) {
repos = repos.filter(r => !r.fork);
}

// Now count repos (only non-forks)
const reposEl = document.getElementById('totalRepos');
if (reposEl) reposEl.textContent = String(repos.length);

// Calculate total stars (only from non-fork repos)
const totalStars = repos.reduce((sum, r) => sum + (r.stargazers_count || 0), 0);
const starsEl = document.getElementById('totalStars');
if (starsEl) starsEl.textContent = String(totalStars);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repository count (and derived totals like stars) are computed from the first page of repos only (per_page=100). For orgs/users with >100 repos, the displayed totals will be incorrect. Consider using the org endpoint’s public_repos for the count and either paginate for star totals or clearly label these as “top 100”.

Copilot uses AI. Check for mistakes.
Comment on lines +217 to +229
// Parse markdown to HTML
function parseMarkdown(md) {
let html = md;
html = html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
html = html.replace(/^&gt; (.+)$/gm, '<blockquote>$1</blockquote>');
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown links are converted into <a href="$2" …> using raw URL text from the README. Because the URL isn’t attribute-escaped or validated, a crafted markdown link can inject attributes (quotes) or use javascript: URLs, leading to XSS. Use a real markdown parser + sanitizer (e.g., marked + DOMPurify as used in readme-viewer.html) or strictly validate/escape link URLs before inserting into innerHTML.

Copilot uses AI. Check for mistakes.
Comment on lines +263 to +274
viewerContent.innerHTML = `
<h1>${repoData.name}</h1>
<p>${repoData.description || 'No description'}</p>
<div class="repo-stats">
<span class="repo-stat">Stars: ${repoData.stargazers_count}</span>
<span class="repo-stat">Forks: ${repoData.forks_count}</span>
<span class="repo-stat">Language: ${repoData.language || 'N/A'}</span>
<span class="repo-stat">Updated: ${new Date(repoData.updated_at).toLocaleDateString()}</span>
</div>
<hr style="border-color: rgba(255,255,255,0.1); margin: 30px 0;">
${readmeHtml || '<p>No README available.</p>'}
`;
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template assigns viewerContent.innerHTML with unescaped data coming from the GitHub API (repoData.name/description) plus generated README HTML. Since viewer.html accepts arbitrary repo URLs via query params, this is effectively untrusted input and can enable XSS. Prefer building DOM nodes with textContent for API fields, and sanitize any rendered markdown before insertion.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants