Initial prototype

This commit is contained in:
2026-03-16 09:16:34 -05:00
parent 2f8bf503f1
commit a0e4f58f62
55 changed files with 13870 additions and 0 deletions
+649
View File
@@ -0,0 +1,649 @@
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('social_refs.json')
.then(response => {
if (!response.ok) {
throw new Error(`Failed to load social_refs.json (${response.status})`);
}
return response.json();
})
.then(data => {
socialRefsByIssueId = normalizeSocialRefs(data);
})
.catch(error => {
console.warn('Social 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 || '',
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;
}
function parseMarkdownIssues(markdownText) {
const lines = String(markdownText || '').split(/\r?\n/);
const issues = [];
let currentIssue = null;
let inCaveatBlock = false;
let caveatLines = [];
lines.forEach(line => {
const issueHeaderMatch = line.match(/^##\s+(.+)$/);
if (issueHeaderMatch) {
finalizeMarkdownIssue(currentIssue, issues);
currentIssue = {
id: issueHeaderMatch[1].trim(),
descriptionLines: [],
caveat: ''
};
inCaveatBlock = false;
caveatLines = [];
return;
}
if (!currentIssue) {
return;
}
if (/^```caveat\s*$/i.test(line.trim())) {
inCaveatBlock = true;
caveatLines = [];
return;
}
if (inCaveatBlock) {
if (/^```\s*$/.test(line.trim())) {
currentIssue.caveat = caveatLines.join(' ').replace(/\s+/g, ' ').trim();
inCaveatBlock = false;
caveatLines = [];
return;
}
const caveatLine = line.trim();
if (caveatLine) {
caveatLines.push(caveatLine);
}
return;
}
const trimmed = line.trim();
if (!trimmed || /^---\s*$/.test(trimmed)) {
return;
}
if (/^(type|product|version|date):\s*/i.test(trimmed)) {
return;
}
currentIssue.descriptionLines.push(trimmed);
});
finalizeMarkdownIssue(currentIssue, issues);
return issues;
}
function finalizeMarkdownIssue(issue, issues) {
if (!issue || !issue.id) {
return;
}
const description = issue.descriptionLines
.map(line => line.trimEnd())
.join('\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
let summary = description;
if (issue.caveat) {
summary = summary
? `[Caveat: ${issue.caveat}] ${summary}`
: `[Caveat: ${issue.caveat}]`;
}
issues.push({
id: issue.id,
summary
});
}
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()
};
}
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()
};
}
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';
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);
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';
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);
container.appendChild(table);
}
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();
if (!id) return;
if (!map.has(id)) {
map.set(id, { id, summary, [versionKey]: [] });
}
const entry = map.get(id);
if (!entry.summary && summary) {
entry.summary = summary;
}
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 parseIssueLine(text) {
const value = String(text || '').trim();
const match = value.match(/^([A-Z]{2,6}-\d{4,8})\s*[:\-]?\s*(.*)$/i);
if (match) {
return {
id: match[1].toUpperCase(),
summary: (match[2] || '').trim()
};
}
return {
id: '',
summary: value
};
}
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);
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 = String(issueId || '').trim().toUpperCase();
if (!normalizedIssueId) {
return [];
}
return socialRefsByIssueId.get(normalizedIssueId) || [];
}
function normalizeSocialRefs(data) {
const map = new Map();
if (!data || typeof data !== 'object') {
return map;
}
Object.keys(data).forEach(sourceKey => {
const sourceValue = data[sourceKey];
const entries = getSourceEntries(sourceValue);
const label = getSourceDisplayLabel(sourceKey, sourceValue);
entries.forEach(entry => {
if (!entry || typeof entry !== 'object') {
return;
}
const id = String(entry.id || '').trim().toUpperCase();
const url = String(entry.url || '').trim();
if (!id || !url) {
return;
}
if (!map.has(id)) {
map.set(id, []);
}
map.get(id).push({ label, url });
});
});
return map;
}
function getSourceEntries(sourceValue) {
if (Array.isArray(sourceValue)) {
return sourceValue;
}
if (sourceValue && typeof sourceValue === 'object' && Array.isArray(sourceValue.entries)) {
return sourceValue.entries;
}
return [];
}
function getSourceDisplayLabel(sourceKey, sourceValue) {
if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue)) {
const display = String(sourceValue.display || '').trim();
if (display) {
return display;
}
}
return sourceLabelFromKey(sourceKey);
}
function sourceLabelFromKey(sourceKey) {
const key = String(sourceKey || '').trim().toLowerCase();
if (!key) {
return 'Link';
}
return key
.split(/[_\-\s]+/)
.filter(Boolean)
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
function markdownSummaryToHtml(summaryText) {
const input = String(summaryText || '').trim();
if (!input) {
return '';
}
const caveatMatch = input.match(/^\[Caveat:\s*([^\]]+)\]\s*/i);
const caveatText = caveatMatch ? caveatMatch[1].trim() : '';
const body = caveatMatch ? input.slice(caveatMatch[0].length) : input;
const htmlParts = [];
if (caveatText) {
htmlParts.push(`<div><em>Caveat: ${escapeHtml(caveatText)}</em></div>`);
}
htmlParts.push(renderMarkdownBodyToHtml(body));
return htmlParts.join('');
}
function renderMarkdownBodyToHtml(markdownText) {
const lines = String(markdownText || '').split(/\r?\n/);
const htmlParts = [];
let paragraphLines = [];
function flushParagraph() {
if (paragraphLines.length === 0) {
return;
}
const text = paragraphLines.join(' ').replace(/\s+/g, ' ').trim();
if (text) {
htmlParts.push(`<p>${escapeHtml(text)}</p>`);
}
paragraphLines = [];
}
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
if (!trimmed) {
flushParagraph();
continue;
}
const next = i + 1 < lines.length ? lines[i + 1].trim() : '';
if (isTableRow(trimmed) && isTableSeparator(next)) {
flushParagraph();
const headerCells = parseMarkdownTableRow(trimmed);
const rows = [];
i += 2;
while (i < lines.length && isTableRow(lines[i].trim())) {
rows.push(parseMarkdownTableRow(lines[i].trim()));
i++;
}
i -= 1;
htmlParts.push(renderHtmlTable(headerCells, rows));
continue;
}
paragraphLines.push(trimmed);
}
flushParagraph();
return htmlParts.join('');
}
function isTableRow(line) {
return /\|/.test(line);
}
function isTableSeparator(line) {
return /^\|?\s*:?-{3,}:?(\s*\|\s*:?-{3,}:?)*\s*\|?$/.test(line);
}
function parseMarkdownTableRow(line) {
const raw = line.trim().replace(/^\|/, '').replace(/\|$/, '');
return raw.split('|').map(cell => cell.trim());
}
function renderHtmlTable(headerCells, rows) {
const colCount = headerCells.length;
const thead = `<thead><tr>${headerCells.map(cell => `<th>${escapeHtml(cell)}</th>`).join('')}</tr></thead>`;
const tbodyRows = rows.map(row => {
const normalized = [...row];
while (normalized.length < colCount) {
normalized.push('');
}
return `<tr>${normalized.slice(0, colCount).map(cell => `<td>${escapeHtml(cell)}</td>`).join('')}</tr>`;
}).join('');
return `<table class="issues-table">${thead}<tbody>${tbodyRows}</tbody></table>`;
}
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderNone(container) {
const li = document.createElement('li');
li.textContent = 'None';
li.style.fontStyle = 'italic';
li.style.color = '#999';
container.appendChild(li);
}
+40
View File
@@ -0,0 +1,40 @@
export function isVersionKey(key) {
return /^\d+(\.\d+)*$/.test(key);
}
export function compareVersionDesc(a, b) {
const aParts = a.split('.').map(Number);
const bParts = b.split('.').map(Number);
const maxLen = Math.max(aParts.length, bParts.length);
for (let i = 0; i < maxLen; i++) {
const aVal = aParts[i] || 0;
const bVal = bParts[i] || 0;
if (aVal !== bVal) {
return bVal - aVal;
}
}
return 0;
}
export function getSortedKeys(obj) {
return Object.keys(obj).sort((a, b) => {
const aIsVersion = isVersionKey(a);
const bIsVersion = isVersionKey(b);
if (aIsVersion && bIsVersion) {
return compareVersionDesc(a, b);
}
if (aIsVersion && !bIsVersion) {
return -1;
}
if (!aIsVersion && bIsVersion) {
return 1;
}
return a.localeCompare(b);
});
}
+229
View File
@@ -0,0 +1,229 @@
import { getSortedKeys } from './sort.js';
let treeContainer = null;
let onSelectionChangeHandler = null;
export function initializeTree(containerId, onSelectionChange) {
treeContainer = document.getElementById(containerId);
onSelectionChangeHandler = onSelectionChange;
}
export function renderProductTree(productsData) {
if (!treeContainer) {
throw new Error('Tree is not initialized. Call initializeTree first.');
}
treeContainer.innerHTML = '';
getSortedKeys(productsData).forEach(product => {
const productNode = createTreeNode(product, productsData[product], [product]);
treeContainer.appendChild(productNode);
});
}
export function getCheckedPaths() {
if (!treeContainer) {
return [];
}
const checkedBoxes = treeContainer.querySelectorAll('input[type="checkbox"]:checked');
return Array.from(checkedBoxes)
.map(cb => cb.dataset.path)
.filter(Boolean)
.map(pathText => {
try {
return JSON.parse(pathText);
} catch (error) {
console.error('Invalid checkbox path data:', pathText, error);
return null;
}
})
.filter(pathArray => Array.isArray(pathArray) && pathArray.length > 0);
}
export function getCheckedFileRefs() {
if (!treeContainer) {
return {
addressedFiles: [],
knownFiles: []
};
}
const addressedFiles = [];
const knownFiles = [];
const checkedBoxes = treeContainer.querySelectorAll('input[type="checkbox"]:checked[data-has-files="1"]');
checkedBoxes.forEach(cb => {
const pathText = cb.dataset.path;
if (!pathText) {
return;
}
let path;
try {
path = JSON.parse(pathText);
} catch (error) {
console.error('Invalid checkbox path data:', pathText, error);
return;
}
if (!Array.isArray(path) || path.length === 0) {
return;
}
const productKey = path[0];
const addressed = parseJsonArray(cb.dataset.addressed);
addressed.forEach(fileName => addressedFiles.push({ productKey, fileName }));
const known = parseJsonArray(cb.dataset.known);
known.forEach(fileName => knownFiles.push({ productKey, fileName }));
});
return {
addressedFiles,
knownFiles
};
}
function createTreeNode(name, value, path) {
const container = document.createElement('div');
container.className = 'tree-item';
const isLeafWithFiles = value && typeof value === 'object' && ('addressed' in value || 'known' in value);
const hasChildren = value && typeof value === 'object' && !isLeafWithFiles && Object.keys(value).length > 0;
if (hasChildren) {
container.classList.add('parent');
const toggle = document.createElement('span');
toggle.className = 'tree-toggle';
toggle.textContent = '▼';
toggle.style.cursor = 'pointer';
const label = document.createElement('label');
const checkbox = createCheckbox(path);
label.appendChild(checkbox);
label.appendChild(document.createTextNode(name));
const childrenContainer = document.createElement('div');
childrenContainer.className = 'tree-children';
getSortedKeys(value).forEach(childKey => {
const childNode = createTreeNode(childKey, value[childKey], [...path, childKey]);
childrenContainer.appendChild(childNode);
});
toggle.addEventListener('click', event => {
event.stopPropagation();
childrenContainer.classList.toggle('collapsed');
toggle.textContent = childrenContainer.classList.contains('collapsed') ? '▶' : '▼';
});
container.appendChild(toggle);
container.appendChild(label);
container.appendChild(childrenContainer);
return container;
}
const label = document.createElement('label');
if (isLeafWithFiles) {
const checkbox = createCheckbox(path, value);
label.appendChild(checkbox);
}
label.appendChild(document.createTextNode(name));
container.appendChild(label);
return container;
}
function createCheckbox(path, value) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.value = path.join('/');
checkbox.dataset.path = JSON.stringify(path);
if (value && typeof value === 'object' && ('addressed' in value || 'known' in value)) {
checkbox.dataset.hasFiles = '1';
checkbox.dataset.addressed = JSON.stringify(Array.isArray(value.addressed) ? value.addressed : []);
checkbox.dataset.known = JSON.stringify(Array.isArray(value.known) ? value.known : []);
}
checkbox.addEventListener('change', event => {
handleCheckboxChange(event);
});
return checkbox;
}
function parseJsonArray(value) {
if (!value) {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch (error) {
console.error('Invalid checkbox file metadata:', value, error);
return [];
}
}
function handleCheckboxChange(event) {
const checkbox = event.target;
const container = checkbox.closest('.tree-item');
const childrenContainer = container.querySelector('.tree-children');
if (childrenContainer) {
const childCheckboxes = childrenContainer.querySelectorAll('input[type="checkbox"]');
childCheckboxes.forEach(cb => {
cb.checked = checkbox.checked;
cb.indeterminate = false;
});
}
checkbox.indeterminate = false;
updateParentCheckboxes(checkbox);
if (typeof onSelectionChangeHandler === 'function') {
onSelectionChangeHandler();
}
}
function updateParentCheckboxes(checkbox) {
let currentItem = checkbox.closest('.tree-item');
let parentItem = currentItem.parentElement.closest('.tree-item');
while (parentItem) {
const parentLabel = parentItem.querySelector(':scope > label');
if (!parentLabel) {
parentItem = parentItem.parentElement.closest('.tree-item');
continue;
}
const parentCheckbox = parentLabel.querySelector('input[type="checkbox"]');
if (!parentCheckbox) {
parentItem = parentItem.parentElement.closest('.tree-item');
continue;
}
const children = parentItem.querySelectorAll(':scope > .tree-children > .tree-item > label > input[type="checkbox"]');
const checkedCount = Array.from(children).filter(cb => cb.checked || cb.indeterminate).length;
if (checkedCount === 0) {
parentCheckbox.checked = false;
parentCheckbox.indeterminate = false;
} else if (checkedCount === children.length) {
parentCheckbox.checked = true;
parentCheckbox.indeterminate = false;
} else {
parentCheckbox.checked = false;
parentCheckbox.indeterminate = true;
}
currentItem = parentItem;
parentItem = currentItem.parentElement.closest('.tree-item');
}
}
+135
View File
@@ -0,0 +1,135 @@
const TYPE_FILTER_KEY = 'bugmedley.ui.typeFilters.v1';
let issueSearchTerm = '';
let issueTypeFilters = loadTypeFilters();
function loadTypeFilters() {
try {
const saved = localStorage.getItem(TYPE_FILTER_KEY);
if (saved) {
const parsed = JSON.parse(saved);
if (typeof parsed.addressed === 'boolean' && typeof parsed.known === 'boolean') {
return parsed;
}
}
} catch (_) {}
return { addressed: true, known: true };
}
function saveTypeFilters() {
try {
localStorage.setItem(TYPE_FILTER_KEY, JSON.stringify(issueTypeFilters));
} catch (_) {}
}
export function initializeUI({ onSelectionChange }) {
initializeIssueSearch();
initializeIssueTypeFilters(onSelectionChange);
updateIssueSectionVisibility();
updateIssueCounts();
}
export function getIssueTypeFilters() {
return issueTypeFilters;
}
export function applyIssueSearchFilter() {
const issueItems = document.querySelectorAll('.issue-search-item');
issueItems.forEach(item => {
const itemText = item.textContent.toLowerCase();
const matches = issueSearchTerm === '' || itemText.includes(issueSearchTerm);
if (item.tagName === 'TR') {
item.style.display = matches ? 'table-row' : 'none';
} else {
item.style.display = matches ? '' : 'none';
}
});
updateIssueCounts();
}
function initializeIssueSearch() {
const searchInput = document.getElementById('issue-search');
searchInput.addEventListener('input', () => {
issueSearchTerm = searchInput.value.trim().toLowerCase();
applyIssueSearchFilter();
});
}
function initializeIssueTypeFilters(onSelectionChange) {
const addressedFilter = document.getElementById('filter-addressed');
const knownFilter = document.getElementById('filter-known');
// Restore checkbox state from persisted filters
addressedFilter.checked = issueTypeFilters.addressed;
knownFilter.checked = issueTypeFilters.known;
addressedFilter.addEventListener('change', () => {
if (!addressedFilter.checked && !knownFilter.checked) {
addressedFilter.checked = true;
}
issueTypeFilters = {
addressed: addressedFilter.checked,
known: knownFilter.checked
};
saveTypeFilters();
updateIssueSectionVisibility();
if (typeof onSelectionChange === 'function') {
onSelectionChange();
}
});
knownFilter.addEventListener('change', () => {
if (!knownFilter.checked && !addressedFilter.checked) {
knownFilter.checked = true;
}
issueTypeFilters = {
addressed: addressedFilter.checked,
known: knownFilter.checked
};
saveTypeFilters();
updateIssueSectionVisibility();
if (typeof onSelectionChange === 'function') {
onSelectionChange();
}
});
}
function updateIssueSectionVisibility() {
const addressedSection = document.getElementById('addressed-section');
const knownSection = document.getElementById('known-section');
addressedSection.style.display = issueTypeFilters.addressed ? 'block' : 'none';
knownSection.style.display = issueTypeFilters.known ? 'block' : 'none';
}
function updateIssueCounts() {
const addressedCount = countVisibleIssueItems('addressed-issues');
const knownCount = countVisibleIssueItems('known-issues');
const addressedCountEl = document.getElementById('addressed-count');
const knownCountEl = document.getElementById('known-count');
if (addressedCountEl) {
addressedCountEl.textContent = `(${addressedCount})`;
}
if (knownCountEl) {
knownCountEl.textContent = `(${knownCount})`;
}
}
function countVisibleIssueItems(containerId) {
const container = document.getElementById(containerId);
if (!container) {
return 0;
}
const items = container.querySelectorAll('.issue-search-item');
return Array.from(items).filter(item => item.style.display !== 'none').length;
}