refactor local match row rendering
This commit is contained in:
@@ -5667,6 +5667,7 @@ test('local match status labels hash matches distinctly from exact matches', ()
|
||||
|
||||
test('local match refreshes preserve keyed rows and button handlers', () => {
|
||||
const renderLocalMatchesContent = extractMethod(searchPanelMethodsSource, 'renderLocalMatchesContent');
|
||||
const renderLocalMatchRow = extractMethod(searchPanelMethodsSource, 'renderLocalMatchRow');
|
||||
const patchLocalMatchesContainer = extractMethod(
|
||||
resolveDownloadMethodsSource,
|
||||
'patchLocalMatchesContainer'
|
||||
@@ -5678,8 +5679,9 @@ test('local match refreshes preserve keyed rows and button handlers', () => {
|
||||
const wireLocalMatchButtons = extractMethod(searchPanelMethodsSource, 'wireLocalMatchButtons');
|
||||
const refreshMissingListRow = extractMethod(missingBrowserMethodsSource, 'refreshMissingListRow');
|
||||
|
||||
assert.match(renderLocalMatchesContent, /data-local-match-key/);
|
||||
assert.match(renderLocalMatchesContent, /encodeURIComponent/);
|
||||
assert.match(renderLocalMatchesContent, /renderLocalMatchRow/);
|
||||
assert.match(renderLocalMatchRow, /data-local-match-key/);
|
||||
assert.match(renderLocalMatchRow, /encodeURIComponent/);
|
||||
assert.match(patchLocalMatchesContainer, /local-match-key|localMatchKey/);
|
||||
assert.match(patchLocalMatchesContainer, /mr-local-alternatives-toggle/);
|
||||
assert.match(refreshLocalMatchesUiForMissing, /patchLocalMatchesContainer/);
|
||||
@@ -5692,6 +5694,76 @@ test('local match refreshes preserve keyed rows and button handlers', () => {
|
||||
assert.match(refreshMissingListRow, /innerHTML !== renderedSources/);
|
||||
});
|
||||
|
||||
test('local match rows preserve best-match and alternative rendering contracts', () => {
|
||||
globalThis.getSvgIcon = () => '<svg></svg>';
|
||||
const renderLocalMatchRow = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchRow')})`);
|
||||
const renderLocalMatchesContent = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchesContent')})`);
|
||||
const dialog = {
|
||||
renderLocalMatchRow,
|
||||
getHashMatchLabelMap() {
|
||||
return null;
|
||||
},
|
||||
buildContextMenuModelData() {
|
||||
return { model: 'context' };
|
||||
},
|
||||
getLocalMatchContextData() {
|
||||
return { source: 'local' };
|
||||
},
|
||||
getLocalMatchIdentity(match) {
|
||||
return match.identity;
|
||||
},
|
||||
escapeHtml(value) {
|
||||
return String(value).replace(/[&<>"']/g, char => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[char]));
|
||||
},
|
||||
getContextMenuAttrs() {
|
||||
return ' data-context-menu="true"';
|
||||
},
|
||||
getConfidenceBadge(confidence) {
|
||||
return `<span class="confidence">${confidence}</span>`;
|
||||
},
|
||||
getModelPreviewTooltipAttrs() {
|
||||
return '';
|
||||
},
|
||||
renderLocalMatchStatusGroup() {
|
||||
return '<span class="status-group">Status</span>';
|
||||
},
|
||||
areLocalMatchAlternativesCollapsed() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
const html = renderLocalMatchesContent.call(dialog, {
|
||||
node_id: 1,
|
||||
widget_index: 2,
|
||||
matches: [
|
||||
{
|
||||
confidence: 100,
|
||||
identity: 'path:/models/best.safetensors',
|
||||
model: { relative_path: 'models/best.safetensors' },
|
||||
},
|
||||
{
|
||||
confidence: 80,
|
||||
identity: 'path:/models/alternative.safetensors',
|
||||
model: { relative_path: 'models/alternative.safetensors' },
|
||||
},
|
||||
],
|
||||
}, 0);
|
||||
|
||||
assert.match(html, /class="mr-match-row mr-best-match"/);
|
||||
assert.match(html, /id="resolve-0-1-2-0"/);
|
||||
assert.match(html, /data-local-match-key="path%3A%2Fmodels%2Fbest\.safetensors"/);
|
||||
assert.match(html, /data-context-menu="true"/g);
|
||||
assert.match(html, /data-local-match-alternative="true"/);
|
||||
assert.match(html, /id="resolve-alt-0-1-2-0"/);
|
||||
assert.match(html, /data-local-match-key="path%3A%2Fmodels%2Falternative\.safetensors"/);
|
||||
assert.equal((html.match(/class="status-group"/g) || []).length, 2);
|
||||
});
|
||||
|
||||
test('URN resolution preserves the download panel container', () => {
|
||||
const resolveUrnAsync = extractMethod(resolveDownloadMethodsSource, 'resolveUrnAsync');
|
||||
|
||||
|
||||
@@ -2358,6 +2358,45 @@ export const searchPanelMethods = {
|
||||
} catch (_e) {}
|
||||
},
|
||||
|
||||
renderLocalMatchRow(missing, match, {
|
||||
missingIndex = 0,
|
||||
matchIndex = 0,
|
||||
hashLabelMap = null,
|
||||
alternative = false,
|
||||
bestMatch = false,
|
||||
} = {}) {
|
||||
const buttonPrefix = alternative ? 'resolve-alt' : 'resolve';
|
||||
const buttonId = `${buttonPrefix}-${missingIndex}-${missing.node_id}-${missing.widget_index}-${matchIndex}`;
|
||||
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
|
||||
const contextModel = this.buildContextMenuModelData(
|
||||
match.model || {},
|
||||
match.filename || '',
|
||||
this.getLocalMatchContextData(missing, match)
|
||||
);
|
||||
const localMatchIdentity = this.getLocalMatchIdentity?.(match) || '';
|
||||
const identityAttr = localMatchIdentity
|
||||
? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"`
|
||||
: '';
|
||||
const fallbackKey = alternative ? `alternative-${matchIndex}` : `match-${matchIndex}`;
|
||||
const localMatchKey = localMatchIdentity
|
||||
|| matchPath
|
||||
|| match.filename
|
||||
|| fallbackKey;
|
||||
const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`;
|
||||
const alternativeAttr = alternative ? ' data-local-match-alternative="true"' : '';
|
||||
const rowClass = `mr-match-row${bestMatch ? ' mr-best-match' : ''}`;
|
||||
|
||||
let html = `<div class="${rowClass}" data-local-match-index="${matchIndex}"${alternativeAttr}${localMatchKeyAttr}${identityAttr}${this.getContextMenuAttrs(contextModel)}>`;
|
||||
html += this.getConfidenceBadge(match.confidence);
|
||||
html += `<span class="mr-match-filename"${this.getModelPreviewTooltipAttrs(match.model || match, matchPath)}>${this.escapeHtml(matchPath)}</span>`;
|
||||
html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap);
|
||||
html += `<button id="${buttonId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">`;
|
||||
html += getSvgIcon('link');
|
||||
html += `</button>`;
|
||||
html += `</div>`;
|
||||
return html;
|
||||
},
|
||||
|
||||
renderLocalMatchesContent(missing, missingIndex = 0) {
|
||||
const allMatches = missing.matches || [];
|
||||
const filteredMatches = allMatches.filter(m => m.confidence >= 70);
|
||||
@@ -2381,33 +2420,13 @@ export const searchPanelMethods = {
|
||||
|
||||
for (let matchIndex = 0; matchIndex < sortedMatches.length; matchIndex++) {
|
||||
const match = sortedMatches[matchIndex];
|
||||
const buttonId = `resolve-${missingIndex}-${missing.node_id}-${missing.widget_index}-${matchIndex}`;
|
||||
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
|
||||
const isBestMatch = matchIndex === 0 && match.confidence >= 95;
|
||||
const contextModel = this.buildContextMenuModelData(
|
||||
match.model || {},
|
||||
match.filename || '',
|
||||
this.getLocalMatchContextData(missing, match)
|
||||
);
|
||||
const localMatchIdentity = this.getLocalMatchIdentity?.(match) || '';
|
||||
const identityAttr = localMatchIdentity
|
||||
? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"`
|
||||
: '';
|
||||
const localMatchKey = localMatchIdentity
|
||||
|| matchPath
|
||||
|| match.filename
|
||||
|| `match-${matchIndex}`;
|
||||
const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`;
|
||||
const rowClass = `mr-match-row${isBestMatch ? ' mr-best-match' : ''}`;
|
||||
|
||||
html += `<div class="${rowClass}" data-local-match-index="${matchIndex}"${localMatchKeyAttr}${identityAttr}${this.getContextMenuAttrs(contextModel)}>`;
|
||||
html += this.getConfidenceBadge(match.confidence);
|
||||
html += `<span class="mr-match-filename"${this.getModelPreviewTooltipAttrs(match.model || match, matchPath)}>${this.escapeHtml(matchPath)}</span>`;
|
||||
html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap);
|
||||
html += `<button id="${buttonId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">`;
|
||||
html += getSvgIcon('link');
|
||||
html += `</button>`;
|
||||
html += `</div>`;
|
||||
html += this.renderLocalMatchRow(missing, match, {
|
||||
missingIndex,
|
||||
matchIndex,
|
||||
hashLabelMap,
|
||||
bestMatch: isBestMatch,
|
||||
});
|
||||
}
|
||||
|
||||
if (perfectMatches.length > 0 && otherMatches.length > 0) {
|
||||
@@ -2422,28 +2441,12 @@ export const searchPanelMethods = {
|
||||
html += `<div id="${matchId}" class="mr-stack-sm ${alternativesCollapsed ? 'mr-hidden' : ''}">`;
|
||||
for (let mIdx = 0; mIdx < otherMatches.length; mIdx++) {
|
||||
const match = otherMatches[mIdx];
|
||||
const altBtnId = `resolve-alt-${missingIndex}-${missing.node_id}-${missing.widget_index}-${mIdx}`;
|
||||
const contextModel = this.buildContextMenuModelData(
|
||||
match.model || {},
|
||||
match.filename || '',
|
||||
this.getLocalMatchContextData(missing, match)
|
||||
);
|
||||
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
|
||||
const localMatchIdentity = this.getLocalMatchIdentity?.(match) || '';
|
||||
const identityAttr = localMatchIdentity
|
||||
? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"`
|
||||
: '';
|
||||
const localMatchKey = localMatchIdentity
|
||||
|| matchPath
|
||||
|| match.filename
|
||||
|| `alternative-${mIdx}`;
|
||||
const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`;
|
||||
html += `<div class="mr-match-row" data-local-match-index="${mIdx}" data-local-match-alternative="true"${localMatchKeyAttr}${identityAttr}${this.getContextMenuAttrs(contextModel)}>`;
|
||||
html += this.getConfidenceBadge(match.confidence);
|
||||
html += `<span class="mr-match-filename"${this.getModelPreviewTooltipAttrs(match.model || match, matchPath)}>${this.escapeHtml(matchPath)}</span>`;
|
||||
html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap);
|
||||
html += `<button id="${altBtnId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">${getSvgIcon('link')}</button>`;
|
||||
html += `</div>`;
|
||||
html += this.renderLocalMatchRow(missing, match, {
|
||||
missingIndex,
|
||||
matchIndex: mIdx,
|
||||
hashLabelMap,
|
||||
alternative: true,
|
||||
});
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user