/**
* Breadcrumb Navigation Utility
*
* Generates navigation path for repository file browsing
*/
/**
* Build breadcrumb navigation data
* @param {object} options
* @param {string} options.username - Repository owner
* @param {string} options.repoName - Repository name
* @param {string} options.ref - Git ref (branch/tag/commit)
* @param {string} options.filePath - Current file/directory path
* @param {string} options.pathPrefix - URL path prefix (e.g., '/git')
* @param {string} options.type - 'tree' or 'blob'
* @returns {Array<{name: string, url: string, isCurrent: boolean}>}
*/
export function buildBreadcrumb(options) {
const {
username,
repoName,
ref = 'HEAD',
filePath = '',
pathPrefix = '',
type = 'tree'
} = options
const parts = []
// Repository root
parts.push({
name: repoName,
url: `${pathPrefix}/${username}/${repoName}`,
isCurrent: !filePath
})
if (filePath) {
const segments = filePath.split('/').filter(s => s)
let currentPath = ''
segments.forEach((segment, index) => {
currentPath += (currentPath ? '/' : '') + segment
const isLast = index === segments.length - 1
parts.push({
name: segment,
url: isLast && type === 'blob'
? `${pathPrefix}/${username}/${repoName}/blob/${currentPath}`
: `${pathPrefix}/${username}/${repoName}/tree/${currentPath}`,
isCurrent: isLast
})
})
}
return parts
}
/**
* Render breadcrumb as HTML
* @param {Array} parts - Breadcrumb parts from buildBreadcrumb
* @param {object} options
* @param {string} options.separator - Separator character
* @param {string} options.icon - Leading icon
* @returns {string} HTML string
*/
export function renderBreadcrumb(parts, options = {}) {
const { separator = '/', icon = '📁' } = options
const links = parts.map((part, index) => {
if (part.isCurrent) {
return `<span class="breadcrumb-current">${escapeHtml(part.name)}</span>`
}
return `<a href="${part.url}" class="breadcrumb-link">${escapeHtml(part.name)}</a>`
})
return `
<nav class="breadcrumb">
<span class="breadcrumb-icon">${icon}</span>
${links.join(`<span class="breadcrumb-separator">${separator}</span>`)}
</nav>
`
}
/**
* Render breadcrumb with branch selector
* @param {Array} parts - Breadcrumb parts
* @param {object} options
* @param {string} options.currentBranch - Current branch/ref
* @param {Array<string>} options.branches - Available branches
* @param {string} options.baseUrl - Base URL for branch switching
* @returns {string} HTML string
*/
export function renderBreadcrumbWithBranch(parts, options = {}) {
const {
currentBranch = 'HEAD',
branches = [],
baseUrl = ''
} = options
const branchSelector = branches.length > 0 ? `
<select class="branch-selector" onchange="switchBranch(this.value)">
${branches.map(b => `
<option value="${b}" ${b === currentBranch ? 'selected' : ''}>${escapeHtml(b)}</option>
`).join('')}
</select>
` : `<span class="branch-name">${escapeHtml(currentBranch)}</span>`
const links = parts.map((part, index) => {
if (index === 0) {
// First part is repo name - add branch selector after it
if (part.isCurrent) {
return `<span class="breadcrumb-current">${escapeHtml(part.name)}</span>${branchSelector}`
}
return `<a href="${part.url}" class="breadcrumb-link">${escapeHtml(part.name)}</a>${branchSelector}`
}
if (part.isCurrent) {
return `<span class="breadcrumb-current">${escapeHtml(part.name)}</span>`
}
return `<a href="${part.url}" class="breadcrumb-link">${escapeHtml(part.name)}</a>`
})
return `
<nav class="breadcrumb">
<span class="breadcrumb-icon">📁</span>
${links.join('<span class="breadcrumb-separator">/</span>')}
</nav>
<script>
function switchBranch(branch) {
const baseUrl = '${baseUrl}';
if (baseUrl) {
window.location.href = baseUrl + '?branch=' + encodeURIComponent(branch);
}
}
</script>
`
}
/**
* Get breadcrumb CSS styles
* @returns {string} CSS string
*/
export function getBreadcrumbStyles() {
return `
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
margin-bottom: 16px;
padding: 12px 16px;
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
flex-wrap: wrap;
}
.breadcrumb-icon {
font-size: 20px;
}
.breadcrumb-link {
color: #58a6ff;
text-decoration: none;
}
.breadcrumb-link:hover {
text-decoration: underline;
}
.breadcrumb-separator {
color: #8b949e;
margin: 0 4px;
}
.breadcrumb-current {
color: #c9d1d9;
font-weight: 600;
}
.branch-selector {
background: #21262d;
color: #c9d1d9;
border: 1px solid #30363d;
border-radius: 6px;
padding: 4px 8px;
font-size: 14px;
margin-left: 8px;
cursor: pointer;
}
.branch-selector:hover {
background: #30363d;
}
.branch-name {
background: #21262d;
color: #c9d1d9;
border-radius: 6px;
padding: 4px 8px;
font-size: 14px;
margin-left: 8px;
}
`
}
// Helper function
function escapeHtml(text) {
if (!text) return ''
return String(text)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
export default {
buildBreadcrumb,
renderBreadcrumb,
renderBreadcrumbWithBranch,
getBreadcrumbStyles
}