From d3425d9d3d0c022d16888c78b413303d2a7ea539 Mon Sep 17 00:00:00 2001 From: xonas1101 Date: Sat, 17 Jan 2026 08:54:13 +0000 Subject: [PATCH 1/7] Added docs source actions (view source filem, open issue) Signed-off-by: xonas1101 --- src/components/docs/DocsLayout.tsx | 7 ++- src/components/docs/DocsSourceActions.tsx | 62 +++++++++++++++++++++++ src/components/docs/EditPageLink.tsx | 54 ++++++++++++-------- 3 files changed, 100 insertions(+), 23 deletions(-) create mode 100644 src/components/docs/DocsSourceActions.tsx diff --git a/src/components/docs/DocsLayout.tsx b/src/components/docs/DocsLayout.tsx index f3c67042..5f44b9e8 100644 --- a/src/components/docs/DocsLayout.tsx +++ b/src/components/docs/DocsLayout.tsx @@ -8,6 +8,7 @@ import { MobileHeader } from './MobileSidebarToggle'; import { EditPageLink } from './EditPageLink'; import { useDocsMenu } from './DocsProvider'; import type { ProjectId } from '@/config/versions'; +import { DocsSourceActions } from '@/components/docs/DocsSourceActions'; interface TOCItem { id: string; @@ -71,7 +72,11 @@ export function DocsLayout({ children, pageMap, toc, metadata, filePath, project {/* Edit page icon - top right */} {filePath && projectId && (
- +
)} diff --git a/src/components/docs/DocsSourceActions.tsx b/src/components/docs/DocsSourceActions.tsx new file mode 100644 index 00000000..95a1eee5 --- /dev/null +++ b/src/components/docs/DocsSourceActions.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { buildGitHubEditUrl } from "./EditPageLink"; +import { useSharedConfig } from "@/hooks/useSharedConfig"; +import type { ProjectId } from "@/config/versions"; + +type DocsSourceActionsProps = { + filePath: string; + projectId: ProjectId; + pageTitle: string; +}; + +export function DocsSourceActions({filePath, projectId, pageTitle,}: DocsSourceActionsProps) { + const { config } = useSharedConfig(); + const editUrl = buildGitHubEditUrl( + filePath, + projectId, + config?.editBaseUrls + ); + + if (!editUrl) return null; + + // convert /edit/ → /blob/ to view source + const sourceUrl = editUrl.replace("/edit/", "/blob/"); + const issueUrl = `https://github.com/kubestellar/docs/issues/new?title=${encodeURIComponent( + `Docs: ${pageTitle}` + )}&body=${encodeURIComponent(`Source file:\n${sourceUrl}`)}`; + + return ( +
+ + View Source + + + + Open Issue + +
+ ); +} diff --git a/src/components/docs/EditPageLink.tsx b/src/components/docs/EditPageLink.tsx index e7c58e3d..9ea27765 100644 --- a/src/components/docs/EditPageLink.tsx +++ b/src/components/docs/EditPageLink.tsx @@ -1,10 +1,18 @@ "use client"; -import { useState, useEffect } from 'react'; -import { useSharedConfig, getVersionsForProject, VersionInfo } from '@/hooks/useSharedConfig'; -import { getProjectVersions as getStaticProjectVersions } from '@/config/versions'; +import { useState } from 'react'; +import { useSharedConfig, VersionInfo } from '@/hooks/useSharedConfig'; import type { ProjectId } from '@/config/versions'; +const STATIC_EDIT_BASE_URLS: Record = { + kubestellar: 'https://github.com/kubestellar/docs/edit/main/docs/content', + a2a: 'https://github.com/kubestellar/a2a/edit/main/docs', + kubeflex: 'https://github.com/kubestellar/kubeflex/edit/main/docs', + 'multi-plugin': 'https://github.com/kubestellar/kubectl-multi-plugin/edit/main/docs', + klaude: 'https://github.com/kubestellar/klaude/edit/main/docs', + console: 'https://github.com/kubestellar/console/edit/main/docs', +}; + interface EditPageLinkProps { filePath: string; projectId: ProjectId; @@ -111,31 +119,33 @@ function isValidGitHubEditUrl(url: string): boolean { } } -export function EditPageLink({ filePath, projectId, variant = 'full' }: EditPageLinkProps) { - const { config } = useSharedConfig(); - const [currentBranch, setCurrentBranch] = useState('main'); +//refactoring helper function to build the GitHub edit URL +export function buildGitHubEditUrl( + filePath: string, + projectId: ProjectId, + editBaseUrls?: Record +): string | null { + const baseUrl = + editBaseUrls?.[projectId] ?? STATIC_EDIT_BASE_URLS[projectId]; - // Get versions to detect current branch - const versions = config - ? getVersionsForProject(config, projectId) - : getStaticProjectVersions(projectId); + if (!baseUrl) return null; - // Detect current branch from hostname on client-side mount - useEffect(() => { - const detected = detectCurrentBranch(versions); - setCurrentBranch(detected); - }, [versions]); + const sanitizedFilePath = filePath.replace(/\.\./g, "").replace(/^\/+/, ""); + return `${baseUrl}/${sanitizedFilePath}`; +} - // Build edit URL with correct branch - const editBaseUrl = buildEditBaseUrl(projectId, currentBranch); +export function EditPageLink({ filePath, projectId, variant = 'full' }: EditPageLinkProps) { + const { config } = useSharedConfig(); + const [currentBranch, setCurrentBranch] = useState('main'); - if (!editBaseUrl) return null; + const editUrl = buildGitHubEditUrl( + filePath, + projectId, + config?.editBaseUrls + ); - // Sanitize filePath to prevent path traversal - const sanitizedFilePath = filePath.replace(/\.\./g, '').replace(/^\/+/, ''); + if (!editUrl) return null; - // Construct the full edit URL - const editUrl = `${editBaseUrl}/${sanitizedFilePath}`; // Validate URL before rendering to prevent XSS if (!isValidGitHubEditUrl(editUrl)) return null; From 36afa01b8f933a8e0dbe718131c61f6da49216cb Mon Sep 17 00:00:00 2001 From: xonas1101 Date: Sat, 17 Jan 2026 09:02:38 +0000 Subject: [PATCH 2/7] Fixed linting errors Signed-off-by: xonas1101 --- src/components/docs/DocsLayout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/docs/DocsLayout.tsx b/src/components/docs/DocsLayout.tsx index 5f44b9e8..42756a57 100644 --- a/src/components/docs/DocsLayout.tsx +++ b/src/components/docs/DocsLayout.tsx @@ -5,7 +5,6 @@ import { DocsSidebar } from './DocsSidebar'; import { TableOfContents } from './TableOfContents'; import { MobileTOC } from './MobileTOC'; import { MobileHeader } from './MobileSidebarToggle'; -import { EditPageLink } from './EditPageLink'; import { useDocsMenu } from './DocsProvider'; import type { ProjectId } from '@/config/versions'; import { DocsSourceActions } from '@/components/docs/DocsSourceActions'; From 1ee1100ccba7eaf41fd5ae2f7961c707b7ac63bd Mon Sep 17 00:00:00 2001 From: xonas1101 Date: Sat, 17 Jan 2026 20:28:22 +0000 Subject: [PATCH 3/7] Added docs source controls to feature cards Signed-off-by: xonas1101 --- src/components/master-page/AboutSection.tsx | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/master-page/AboutSection.tsx b/src/components/master-page/AboutSection.tsx index d017fe2d..75c72975 100644 --- a/src/components/master-page/AboutSection.tsx +++ b/src/components/master-page/AboutSection.tsx @@ -3,6 +3,7 @@ import { useEffect } from "react"; import { GridLines, StarField } from "../index"; import { useTranslations } from "next-intl"; +import { DocsSourceActions } from "@/components/docs/DocsSourceActions"; import Link from "next/link"; export default function AboutSection() { @@ -175,6 +176,13 @@ export default function AboutSection() { +
+ +
@@ -231,6 +239,13 @@ export default function AboutSection() { +
+ +
@@ -287,6 +302,13 @@ export default function AboutSection() { +
+ +
@@ -294,4 +316,4 @@ export default function AboutSection() { ); -} +} \ No newline at end of file From c7c4b9dde9f70244c060a75adc68510c465223d8 Mon Sep 17 00:00:00 2001 From: xonas1101 Date: Sat, 17 Jan 2026 20:43:28 +0000 Subject: [PATCH 4/7] Added docs source controls to feature cards Signed-off-by: xonas1101 --- src/components/master-page/AboutSection.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/master-page/AboutSection.tsx b/src/components/master-page/AboutSection.tsx index 75c72975..b259e553 100644 --- a/src/components/master-page/AboutSection.tsx +++ b/src/components/master-page/AboutSection.tsx @@ -128,7 +128,7 @@ export default function AboutSection() {
-
+
{/* Icon with animation */}
@@ -191,7 +191,7 @@ export default function AboutSection() {
-
+
{/* Icon with animation */}
@@ -254,7 +254,7 @@ export default function AboutSection() {
-
+
{/* Icon with animation */}
From 453fc9b3529860d4e44b253279ece492f7de913b Mon Sep 17 00:00:00 2001 From: xonas1101 Date: Tue, 20 Jan 2026 09:41:32 +0000 Subject: [PATCH 5/7] Added composing a PR functionality Signed-off-by: xonas1101 --- src/components/docs/DocsSourceActions.tsx | 146 ++++++++++++++++------ 1 file changed, 109 insertions(+), 37 deletions(-) diff --git a/src/components/docs/DocsSourceActions.tsx b/src/components/docs/DocsSourceActions.tsx index 95a1eee5..a506a3be 100644 --- a/src/components/docs/DocsSourceActions.tsx +++ b/src/components/docs/DocsSourceActions.tsx @@ -1,62 +1,134 @@ "use client"; -import { buildGitHubEditUrl } from "./EditPageLink"; import { useSharedConfig } from "@/hooks/useSharedConfig"; import type { ProjectId } from "@/config/versions"; +/* ------------------------------------------------------------------ + Static edit base URLs (fallback if shared config unavailable) +------------------------------------------------------------------- */ +const STATIC_EDIT_BASE_URLS: Record = { + kubestellar: "https://github.com/kubestellar/docs/edit/main/docs/content", + a2a: "https://github.com/kubestellar/a2a/edit/main/docs", + kubeflex: "https://github.com/kubestellar/kubeflex/edit/main/docs", + "multi-plugin": "https://github.com/kubestellar/kubectl-multi-plugin/edit/main/docs", + "kubectl-claude": "https://github.com/kubestellar/kubectl-claude/edit/main/docs", +}; + +/* ------------------------------------------------------------------ + Helpers (kept LOCAL to this file as requested) +------------------------------------------------------------------- */ + +// Prevent path traversal +function sanitizeFilePath(filePath: string): string { + return filePath.replace(/\.\./g, "").replace(/^\/+/, ""); +} + +// Force fork-based editing for ALL users (including admins) +function buildGitHubEditUrl( + filePath: string, + projectId: ProjectId, + editBaseUrls?: Record +): string | null { + const baseUrl = + editBaseUrls?.[projectId] ?? STATIC_EDIT_BASE_URLS[projectId]; + + if (!baseUrl) return null; + + return `${baseUrl}/${sanitizeFilePath(filePath)}?fork=true`; +} + +// Validate GitHub edit URL (XSS protection) +function isValidGitHubEditUrl(url: string): boolean { + try { + const parsed = new URL(url); + return ( + parsed.protocol === "https:" && + parsed.hostname === "github.com" && + parsed.pathname.includes("/edit/") + ); + } catch { + return false; + } +} + +// Convert edit → blob (remove fork param) +function buildSourceUrl(editUrl: string): string { + const url = new URL(editUrl); + url.search = ""; + url.pathname = url.pathname.replace("/edit/", "/blob/"); + return url.toString(); +} + +// Build GitHub issue link +function buildIssueUrl(pageTitle: string, sourceUrl: string): string { + return `https://github.com/kubestellar/docs/issues/new?title=${encodeURIComponent( + `Docs: ${pageTitle}` + )}&body=${encodeURIComponent(`Source file:\n${sourceUrl}`)}`; +} + +/* ------------------------------------------------------------------ + Component +------------------------------------------------------------------- */ + type DocsSourceActionsProps = { filePath: string; projectId: ProjectId; pageTitle: string; }; -export function DocsSourceActions({filePath, projectId, pageTitle,}: DocsSourceActionsProps) { +export function DocsSourceActions({ + filePath, + projectId, + pageTitle, +}: DocsSourceActionsProps) { const { config } = useSharedConfig(); + const editUrl = buildGitHubEditUrl( filePath, projectId, config?.editBaseUrls ); - if (!editUrl) return null; + if (!editUrl || !isValidGitHubEditUrl(editUrl)) return null; - // convert /edit/ → /blob/ to view source - const sourceUrl = editUrl.replace("/edit/", "/blob/"); - const issueUrl = `https://github.com/kubestellar/docs/issues/new?title=${encodeURIComponent( - `Docs: ${pageTitle}` - )}&body=${encodeURIComponent(`Source file:\n${sourceUrl}`)}`; + const safeEditUrl = new URL(editUrl).href; + const sourceUrl = buildSourceUrl(safeEditUrl); + const issueUrl = buildIssueUrl(pageTitle, sourceUrl); return (
- - View Source - - - - Open Issue - + Compose a PR + View Source + Open Issue
); } + +/* ------------------------------------------------------------------ + Small shared button +------------------------------------------------------------------- */ + +function ActionLink({ + href, + children, +}: { + href: string; + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} From c681f67bf2b9a00e893fbce9aedcc4fd695a6551 Mon Sep 17 00:00:00 2001 From: xonas1101 Date: Tue, 20 Jan 2026 10:19:34 +0000 Subject: [PATCH 6/7] fix: adjust DocsSourceActions after rebase Signed-off-by: xonas1101 --- src/components/docs/DocsSourceActions.tsx | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/components/docs/DocsSourceActions.tsx b/src/components/docs/DocsSourceActions.tsx index a506a3be..e17600ef 100644 --- a/src/components/docs/DocsSourceActions.tsx +++ b/src/components/docs/DocsSourceActions.tsx @@ -3,21 +3,15 @@ import { useSharedConfig } from "@/hooks/useSharedConfig"; import type { ProjectId } from "@/config/versions"; -/* ------------------------------------------------------------------ - Static edit base URLs (fallback if shared config unavailable) -------------------------------------------------------------------- */ const STATIC_EDIT_BASE_URLS: Record = { kubestellar: "https://github.com/kubestellar/docs/edit/main/docs/content", a2a: "https://github.com/kubestellar/a2a/edit/main/docs", kubeflex: "https://github.com/kubestellar/kubeflex/edit/main/docs", "multi-plugin": "https://github.com/kubestellar/kubectl-multi-plugin/edit/main/docs", - "kubectl-claude": "https://github.com/kubestellar/kubectl-claude/edit/main/docs", + klaude: "https://github.com/kubestellar/kubectl-claude/edit/main/docs", + console: 'https://github.com/kubestellar/console/edit/main/docs', }; -/* ------------------------------------------------------------------ - Helpers (kept LOCAL to this file as requested) -------------------------------------------------------------------- */ - // Prevent path traversal function sanitizeFilePath(filePath: string): string { return filePath.replace(/\.\./g, "").replace(/^\/+/, ""); @@ -37,7 +31,7 @@ function buildGitHubEditUrl( return `${baseUrl}/${sanitizeFilePath(filePath)}?fork=true`; } -// Validate GitHub edit URL (XSS protection) +// Validate GitHub edit URL function isValidGitHubEditUrl(url: string): boolean { try { const parsed = new URL(url); @@ -51,7 +45,7 @@ function isValidGitHubEditUrl(url: string): boolean { } } -// Convert edit → blob (remove fork param) +// Convert edit → blob function buildSourceUrl(editUrl: string): string { const url = new URL(editUrl); url.search = ""; @@ -66,10 +60,6 @@ function buildIssueUrl(pageTitle: string, sourceUrl: string): string { )}&body=${encodeURIComponent(`Source file:\n${sourceUrl}`)}`; } -/* ------------------------------------------------------------------ - Component -------------------------------------------------------------------- */ - type DocsSourceActionsProps = { filePath: string; projectId: ProjectId; @@ -104,10 +94,6 @@ export function DocsSourceActions({ ); } -/* ------------------------------------------------------------------ - Small shared button -------------------------------------------------------------------- */ - function ActionLink({ href, children, From 27dde796bae780a5bf8773ce1bf2e0284d455421 Mon Sep 17 00:00:00 2001 From: xonas1101 Date: Tue, 20 Jan 2026 12:23:06 +0000 Subject: [PATCH 7/7] fix: adjust EditPageLink after rebase Signed-off-by: xonas1101 --- src/components/docs/EditPageLink.tsx | 89 +--------------------------- 1 file changed, 1 insertion(+), 88 deletions(-) diff --git a/src/components/docs/EditPageLink.tsx b/src/components/docs/EditPageLink.tsx index 9ea27765..100e029d 100644 --- a/src/components/docs/EditPageLink.tsx +++ b/src/components/docs/EditPageLink.tsx @@ -1,7 +1,6 @@ "use client"; -import { useState } from 'react'; -import { useSharedConfig, VersionInfo } from '@/hooks/useSharedConfig'; +import { useSharedConfig } from '@/hooks/useSharedConfig'; import type { ProjectId } from '@/config/versions'; const STATIC_EDIT_BASE_URLS: Record = { @@ -19,91 +18,6 @@ interface EditPageLinkProps { variant?: 'full' | 'icon'; } -// Version entry with key from the versions config -type VersionEntry = { key: string } & VersionInfo; - -// Convert branch name to Netlify slug format (e.g., docs/0.29.0 -> docs-0-29-0) -function branchToSlug(branch: string): string { - return branch.replace(/\//g, '-').replace(/\./g, '-'); -} - -// Detect current branch from hostname (for kubestellar docs repo) -function detectCurrentBranch(versions: VersionEntry[]): string { - if (typeof window === 'undefined') return 'main'; - - const hostname = window.location.hostname; - - // Production site uses the "latest" version's branch - if (hostname === 'kubestellar.io' || hostname === 'www.kubestellar.io') { - const latestVersion = versions.find(v => v.key === 'latest'); - return latestVersion?.branch || 'main'; - } - - // Netlify branch deploys: {branch-slug}--{site-name}.netlify.app - const branchDeployMatch = hostname.match(/^(.+)--[\w-]+\.netlify\.app$/); - if (branchDeployMatch) { - const branchSlug = branchDeployMatch[1]; - - // Main branch deploy - if (branchSlug === 'main') { - return 'main'; - } - - // Deploy previews go to main - if (branchSlug.startsWith('deploy-preview-')) { - return 'main'; - } - - // Match branch slug to version branch (e.g., docs-0-29-0 -> docs/0.29.0) - for (const version of versions) { - if (branchSlug === branchToSlug(version.branch)) { - return version.branch; - } - } - } - - return 'main'; -} - -// Source repos for each project (used when on main branch) -// Projects not listed here have their docs in the docs repo itself -const SOURCE_REPOS: Record = { - a2a: { repo: 'kubestellar/a2a', docsPath: 'docs' }, - kubeflex: { repo: 'kubestellar/kubeflex', docsPath: 'docs' }, - 'multi-plugin': { repo: 'kubestellar/kubectl-multi-plugin', docsPath: 'docs' }, - 'klaude': { repo: 'kubestellar/klaude', docsPath: 'docs' }, -}; - -// Projects whose docs live in the docs repo itself (not a separate source repo) -const DOCS_REPO_PROJECTS = ['console']; - -// Build edit URL for a project, using correct branch -function buildEditBaseUrl(projectId: ProjectId, branch: string): string { - // KubeStellar docs always live in docs repo - if (projectId === 'kubestellar') { - return `https://github.com/kubestellar/docs/edit/${branch}/docs/content`; - } - - // Projects whose docs live in the docs repo itself (e.g., console) - if (DOCS_REPO_PROJECTS.includes(projectId)) { - return `https://github.com/kubestellar/docs/edit/${branch}/docs/content/${projectId}`; - } - - // For other projects: version branches are in docs repo, main goes to source repo - if (branch !== 'main' && branch.startsWith('docs/')) { - // Version branch in docs repo (e.g., docs/klaude/0.6.0) - return `https://github.com/kubestellar/docs/edit/${branch}/docs/content/${projectId}`; - } - - // Main branch - link to source repo - const source = SOURCE_REPOS[projectId]; - if (source) { - return `https://github.com/${source.repo}/edit/main/${source.docsPath}`; - } - - return ''; -} - // Validate that URL is a safe GitHub edit URL to prevent XSS function isValidGitHubEditUrl(url: string): boolean { try { @@ -136,7 +50,6 @@ export function buildGitHubEditUrl( export function EditPageLink({ filePath, projectId, variant = 'full' }: EditPageLinkProps) { const { config } = useSharedConfig(); - const [currentBranch, setCurrentBranch] = useState('main'); const editUrl = buildGitHubEditUrl( filePath,