import { stripMarkdownFrontmatter } from './markdown.js'; import { BLANK_ISSUE_ID, normalizeIssueId, normalizeWhitespace, parseIssueLine as parseIssueLineValue } from './issue-id.js'; import { marked, Renderer } from '../vendor/marked.esm.js'; const summaryMarkdownRenderer = new Renderer(); summaryMarkdownRenderer.html = token => { return escapeHtml(typeof token === 'string' ? token : (token.raw || token.text || '')); }; export function clearIssues() { document.getElementById('addressed-issues').innerHTML = ''; document.getElementById('known-issues').innerHTML = ''; } let socialRefsByIssueId = new Map(); const socialRefsReady = loadSocialRefs(); const issueFileDataCache = new Map(); const issueFilePromiseCache = new Map(); function loadSocialRefs() { return fetch('data/external_refs.json') .then(response => { if (!response.ok) { throw new Error(`Failed to load data/external_refs.json (${response.status})`); } return response.json(); }) .then(data => { socialRefsByIssueId = normalizeSocialRefs(data); }) .catch(error => { console.warn('External refs unavailable:', error); socialRefsByIssueId = new Map(); }); } export function loadIssuesForCheckedPaths({ issueTypeFilters, checkedFileRefs, applyIssueSearchFilter }) { const addressedFiles = checkedFileRefs && typeof checkedFileRefs === 'object' && Array.isArray(checkedFileRefs.addressedFiles) ? checkedFileRefs.addressedFiles : []; const knownFiles = checkedFileRefs && typeof checkedFileRefs === 'object' && Array.isArray(checkedFileRefs.knownFiles) ? checkedFileRefs.knownFiles : []; if (addressedFiles.length === 0 && knownFiles.length === 0) { clearIssues(); applyIssueSearchFilter(); return; } if (issueTypeFilters.addressed) { loadIssuesFromFiles(addressedFiles, 'addressed', 'addressed-issues', applyIssueSearchFilter); } else { document.getElementById('addressed-issues').innerHTML = ''; } if (issueTypeFilters.known) { loadIssuesFromFiles(knownFiles, 'known', 'known-issues', applyIssueSearchFilter); } else { document.getElementById('known-issues').innerHTML = ''; } } function loadIssuesFromFiles(fileRefs, issueType, elementId, applyIssueSearchFilter) { const issuesContainer = document.getElementById(elementId); issuesContainer.innerHTML = ''; if (!fileRefs || fileRefs.length === 0) { renderNone(issuesContainer); applyIssueSearchFilter(); return; } const allIssues = []; let loadedCount = 0; fileRefs.forEach(fileRef => { fetchIssueFile(fileRef.productKey, fileRef.fileName, issueType) .then(data => { const sourceVersion = getSourceVersionFromFileName(fileRef.fileName); normalizeIssuesFromPayload(data).forEach(issue => { allIssues.push({ id: issue.id || '', summary: issue.summary || '', resolved: issue.resolved || '', caveat: issue.caveat || '', sourceVersion }); }); }) .catch(error => { console.error(`Error loading ${fileRef.fileName}:`, error); }) .finally(() => { loadedCount++; if (loadedCount === fileRefs.length) { socialRefsReady.finally(() => { displayIssues(allIssues, issueType, elementId); applyIssueSearchFilter(); }); } }); }); } function fetchIssueFile(productKey, fileName, issueType) { const filePath = `data/issues/${productKey}/${issueType}/${fileName}`; if (issueFileDataCache.has(filePath)) { return Promise.resolve(issueFileDataCache.get(filePath)); } if (issueFilePromiseCache.has(filePath)) { return issueFilePromiseCache.get(filePath); } const request = fetch(filePath) .then(response => { if (!response.ok) { throw new Error(`Failed to load ${fileName}`); } if (/\.md$/i.test(fileName)) { return response.text().then(text => parseMarkdownIssues(text)); } return response.json(); }) .then(data => { issueFileDataCache.set(filePath, data); issueFilePromiseCache.delete(filePath); return data; }) .catch(error => { issueFilePromiseCache.delete(filePath); throw error; }); issueFilePromiseCache.set(filePath, request); return request; } export function parseMarkdownIssues(markdownText) { const input = String(stripMarkdownFrontmatter(markdownText) || ''); if (!input.trim()) { return []; } try { const tokens = marked.lexer(input, { gfm: true }); const issues = []; let currentIssueId = ''; let currentIssueBodyParts = []; let currentIssueResolvedBlocks = []; let currentIssueCaveatBlocks = []; const finalizeIssue = () => { if (!currentIssueId) { return; } const summaryBody = currentIssueBodyParts .join('') .replace(/\n{3,}/g, '\n\n') .trim(); const summary = summaryBody; const resolved = currentIssueResolvedBlocks.join('\n\n').trim(); const caveat = currentIssueCaveatBlocks.join('\n\n').trim(); issues.push({ id: currentIssueId, summary, resolved, caveat }); }; tokens.forEach(token => { if (token.type === 'heading' && token.depth === 2) { finalizeIssue(); currentIssueId = String(token.text || '').trim(); currentIssueBodyParts = []; currentIssueResolvedBlocks = []; currentIssueCaveatBlocks = []; return; } if (!currentIssueId) { return; } if (token.type === 'code' && String(token.lang || '').trim().toLowerCase() === 'resolved') { const resolvedText = String(token.text || '').trim(); if (resolvedText) { currentIssueResolvedBlocks.push(resolvedText); } return; } if (token.type === 'code' && String(token.lang || '').trim().toLowerCase() === 'caveat') { const caveatText = String(token.text || '').trim(); if (caveatText) { currentIssueCaveatBlocks.push(caveatText); } return; } if (token.type === 'space' || token.type === 'hr') { return; } if (typeof token.raw === 'string' && token.raw) { currentIssueBodyParts.push(token.raw); return; } if (typeof token.text === 'string' && token.text.trim()) { currentIssueBodyParts.push(`${token.text.trim()}\n\n`); } }); finalizeIssue(); return issues; } catch (error) { console.error('Marked token parser failed.', error); return []; } } function normalizeIssuesFromPayload(data) { if (Array.isArray(data)) { return data.map(item => { if (typeof item === 'string') { return parseIssueLine(item); } if (item && typeof item === 'object') { if (item.id && (item.description || item.summary)) { return { id: String(item.id).trim(), summary: String(item.summary || item.description).trim(), resolved: String(item.resolved || '').trim(), caveat: String(item.caveat || '').trim() }; } return parseIssueLine(JSON.stringify(item)); } return parseIssueLine(String(item)); }); } if (data && typeof data === 'object' && Array.isArray(data.issues)) { return data.issues.map(issue => { if (issue && typeof issue === 'object' && issue.id && issue.description) { return { id: String(issue.id).trim(), summary: String(issue.description).trim(), resolved: String(issue.resolved || '').trim(), caveat: String(issue.caveat || '').trim() }; } return parseIssueLine(JSON.stringify(issue)); }); } if (data && typeof data === 'object') { return [parseIssueLine(JSON.stringify(data))]; } return []; } function displayIssues(issues, issueType, elementId) { const issuesContainer = document.getElementById(elementId); issuesContainer.innerHTML = ''; if (issues.length === 0) { renderNone(issuesContainer); return; } if (issueType === 'addressed') { renderAddressedTable(issues, issuesContainer); return; } renderKnownList(issues, issuesContainer); } function renderAddressedTable(issues, container) { const deduped = dedupeAddressedIssues(issues); if (deduped.length === 0) { renderNone(container); return; } const table = document.createElement('table'); table.className = 'issues-table issue-list-table'; const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); ['Issue ID', 'Addressed in', 'Summary'].forEach((heading, idx) => { const th = document.createElement('th'); th.textContent = heading; if (idx === 0) { th.className = 'issue-id-col'; } if (idx === 1) { th.className = 'issue-version-col'; } headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement('tbody'); deduped.forEach(issue => { const row = document.createElement('tr'); row.className = 'issue-search-item'; const idCell = document.createElement('td'); idCell.className = 'issue-id-col'; idCell.textContent = issue.id; const addressedInCell = document.createElement('td'); addressedInCell.className = 'issue-version-col'; addressedInCell.textContent = issue.addressedIn.join(', '); const summaryCell = document.createElement('td'); renderSummaryCell(summaryCell, issue); row.appendChild(idCell); row.appendChild(addressedInCell); row.appendChild(summaryCell); tbody.appendChild(row); }); table.appendChild(tbody); addTableCellLabels(table); container.appendChild(table); } function renderKnownList(issues, container) { const deduped = dedupeIssues(issues, 'knownIn'); if (deduped.length === 0) { renderNone(container); return; } const table = document.createElement('table'); table.className = 'issues-table issue-list-table'; const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); ['Issue ID', 'Known in', 'Summary'].forEach((heading, idx) => { const th = document.createElement('th'); th.textContent = heading; if (idx === 0) th.className = 'issue-id-col'; if (idx === 1) th.className = 'issue-version-col'; headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement('tbody'); deduped.forEach(issue => { const row = document.createElement('tr'); row.className = 'issue-search-item'; const idCell = document.createElement('td'); idCell.className = 'issue-id-col'; idCell.textContent = issue.id; const knownInCell = document.createElement('td'); knownInCell.className = 'issue-version-col'; knownInCell.textContent = issue.knownIn.join(', '); const summaryCell = document.createElement('td'); renderSummaryCell(summaryCell, issue); row.appendChild(idCell); row.appendChild(knownInCell); row.appendChild(summaryCell); tbody.appendChild(row); }); table.appendChild(tbody); addTableCellLabels(table); container.appendChild(table); } function addTableCellLabels(table) { const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim()); if (headers.length === 0) { return; } table.querySelectorAll('tbody tr').forEach(row => { Array.from(row.children).forEach((cell, idx) => { if (!(cell instanceof HTMLElement)) { return; } const label = headers[idx] || `Column ${idx + 1}`; cell.setAttribute('data-label', label); }); }); } function dedupeAddressedIssues(issues) { return dedupeIssues(issues, 'addressedIn'); } function dedupeIssues(issues, versionKey) { const map = new Map(); issues.forEach(issue => { const id = (issue.id || '').trim(); const summary = (issue.summary || '').trim(); const resolved = (issue.resolved || '').trim(); const caveat = (issue.caveat || '').trim(); if (!id) return; const dedupeKey = getIssueDedupeKey(id, summary, resolved, caveat); if (!map.has(dedupeKey)) { map.set(dedupeKey, { id, summary, resolved, caveat, [versionKey]: [] }); } const entry = map.get(dedupeKey); if (!entry.summary && summary) { entry.summary = summary; } if (!entry.resolved && resolved) { entry.resolved = resolved; } if (!entry.caveat && caveat) { entry.caveat = caveat; } const version = (issue.sourceVersion || '').trim(); if (version && !entry[versionKey].includes(version)) { entry[versionKey].push(version); } }); return Array.from(map.values()).sort((a, b) => b.id.localeCompare(a.id)); } function getIssueDedupeKey(id, summary, resolved, caveat) { const normalizedId = normalizeIssueId(id); if (normalizedId !== BLANK_ISSUE_ID) { return normalizedId; } const normalizedSummary = normalizeWhitespace(summary); const normalizedResolved = normalizeWhitespace(resolved); const normalizedCaveat = normalizeWhitespace(caveat); return `${normalizedId}|${normalizedSummary}|${normalizedResolved}|${normalizedCaveat}`; } function parseIssueLine(text) { return parseIssueLineValue(text); } function getSourceVersionFromFileName(fileName) { const value = String(fileName || '').trim(); const underscoreIndex = value.indexOf('_'); if (underscoreIndex > 0) { return value.slice(0, underscoreIndex); } return value.replace(/\.(json|md)$/i, ''); } function renderSummaryCell(cell, issue) { cell.innerHTML = markdownSummaryToHtml(issue.summary, issue.resolved, issue.caveat); const refs = getSocialRefsForIssue(issue.id); if (!refs.length) { return; } const refsContainer = document.createElement('div'); refsContainer.style.marginTop = '6px'; refs.forEach((ref, index) => { const link = document.createElement('a'); link.href = ref.url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = `(${ref.label})`; refsContainer.appendChild(link); if (index < refs.length - 1) { refsContainer.appendChild(document.createTextNode(' ')); } }); cell.appendChild(refsContainer); } function getSocialRefsForIssue(issueId) { const normalizedIssueId = normalizeIssueId(issueId); if (!normalizedIssueId) { return []; } return socialRefsByIssueId.get(normalizedIssueId) || []; } function normalizeSocialRefs(data) { const map = new Map(); if (!Array.isArray(data)) { return map; } data.forEach(entry => { if (!entry || typeof entry !== 'object') { return; } const id = String(entry.id || '').trim().toUpperCase(); const url = String(entry.url || '').trim(); const label = String(entry.display || '').trim() || 'Link'; if (!id || !url) { return; } if (!map.has(id)) { map.set(id, []); } map.get(id).push({ label, url }); }); return map; } export function markdownSummaryToHtml(summaryText, resolvedText, caveatText) { const input = String(summaryText || '').trim(); const resolved = String(resolvedText || '').trim(); const caveat = String(caveatText || '').trim(); if (!input && !resolved && !caveat) { return ''; } const htmlParts = []; if (resolved) { htmlParts.push(`