/**
* Shared Markdown & Label Utilities
*
* Extracted from IssueManager for reuse across issue backends.
*/
/**
* Default labels created for new repositories
*/
export const DEFAULT_LABELS = [
{ name: 'bug', color: '#d73a4a', description: 'Something isn\'t working' },
{ name: 'enhancement', color: '#a2eeef', description: 'New feature or request' },
{ name: 'documentation', color: '#0075ca', description: 'Improvements or additions to documentation' },
{ name: 'question', color: '#d876e3', description: 'Further information is requested' },
{ name: 'help-wanted', color: '#008672', description: 'Extra attention is needed' },
{ name: 'good-first-issue', color: '#7057ff', description: 'Good for newcomers' },
{ name: 'duplicate', color: '#cfd3d7', description: 'This issue already exists' },
{ name: 'wontfix', color: '#ffffff', description: 'This will not be worked on' }
]
/**
* Get label color styles for display
* @param {string} color - Hex color
* @returns {Object} CSS styles { background, foreground }
*/
export function getLabelStyles (color) {
if (!color) {
return { background: '#ccc', foreground: '#000' }
}
const hex = color.replace('#', '')
const r = parseInt(hex.substr(0, 2), 16)
const g = parseInt(hex.substr(2, 2), 16)
const b = parseInt(hex.substr(4, 2), 16)
const brightness = (r * 299 + g * 587 + b * 114) / 1000
return {
background: color,
foreground: brightness > 125 ? '#000000' : '#ffffff'
}
}
/**
* Parse markdown with image references to HTML
* @param {string} markdown - Markdown text
* @param {Object} images - Hash -> data URL map
* @returns {string} HTML
*/
export function parseMarkdown (markdown, images = {}) {
if (!markdown) return ''
// Escape HTML
let html = markdown
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
// Code blocks first (with language support): ```lang\ncode```
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => {
return `<pre><code class="language-${lang || 'plaintext'}">${code.trim()}</code></pre>`
})
// Inline code: `code`
html = html.replace(/`([^`]+)`/g, '<code>$1</code>')
// Headers (must be at start of line)
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>')
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>')
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>')
// Bold and italic (order matters - bold+italic first)
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
html = html.replace(/___(.+?)___/g, '<strong><em>$1</em></strong>')
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
html = html.replace(/__(.+?)__/g, '<strong>$1</strong>')
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>')
html = html.replace(/_(.+?)_/g, '<em>$1</em>')
// Links: [text](url) - but not images
html = html.replace(/(?<!!)\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
// Images:  -> <img>
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, hash) => {
const dataUrl = images[hash]
if (dataUrl) {
return `<img src="${dataUrl}" alt="${alt || 'Image'}" class="issue-image" />`
}
return match // Keep original if image not found
})
// Unordered lists
html = html.replace(/^[\*\-] (.+)$/gm, '<li>$1</li>')
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
// Ordered lists
html = html.replace(/^\d+\. (.+)$/gm, '<li>$1</li>')
// Blockquotes
html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
// Horizontal rules
html = html.replace(/^---$/gm, '<hr>')
html = html.replace(/^\*\*\*$/gm, '<hr>')
// Paragraphs (double newline = new paragraph)
html = html.replace(/\n\n/g, '</p><p>')
html = '<p>' + html + '</p>'
// Single line breaks
html = html.replace(/\n/g, '<br>')
// Clean up empty paragraphs
html = html.replace(/<p><\/p>/g, '')
html = html.replace(/<p>\s*<\/p>/g, '')
return html
}