refactor: centralize context-menu attribute and element helpers
Replace scattered inline encodeURIComponent(JSON.stringify(...)) and
manual oncontextmenu/dataset assignments with unified helpers added to
renderFormatMethods:
- encodeContextMenuModel(context): safe JSON serialization for data-model
- getContextMenuAttrs(context, tooltip): HTML attribute string for
template literals (used in 5 files)
- getContextMenuProps(context, tooltip): props object for imperative
element assignment (used in missingBrowserMethods)
- setContextMenuElement(element, context, tooltip): sets or clears
data-model, data-tooltip, and oncontextmenu on a DOM element
Reduces duplication across 5 modules, adds try/catch around JSON
serialization, and removes 14 lines of repeated oncontextmenu wiring.
This commit is contained in:
@@ -1161,7 +1161,7 @@ export const queueMethods = {
|
||||
? 'Right-click for workflow and download folder actions'
|
||||
: 'Right-click to switch to workflow';
|
||||
const contextData = contextModel
|
||||
? ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(contextModel)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="${this.escapeHtml(contextTooltip)}"`
|
||||
? this.getContextMenuAttrs(contextModel, contextTooltip)
|
||||
: '';
|
||||
|
||||
html += `<div class="mr-queue-item mr-download-queue-item"${contextData}>`;
|
||||
@@ -1215,7 +1215,7 @@ export const queueMethods = {
|
||||
? 'Right-click for workflow and download folder actions'
|
||||
: (hasWorkflowAction ? 'Right-click to switch to workflow' : 'Right-click to open download folder');
|
||||
const contextData = contextModel
|
||||
? ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(contextModel)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="${this.escapeHtml(contextTooltip)}"`
|
||||
? this.getContextMenuAttrs(contextModel, contextTooltip)
|
||||
: '';
|
||||
const historyId = String(entry.id || '');
|
||||
|
||||
@@ -1322,9 +1322,7 @@ export const queueMethods = {
|
||||
if (!selection) {
|
||||
selectedBar.style.display = 'none';
|
||||
selectedBar.innerHTML = '';
|
||||
selectedBar.removeAttribute('data-model');
|
||||
selectedBar.removeAttribute('data-tooltip');
|
||||
selectedBar.oncontextmenu = null;
|
||||
this.setContextMenuElement(selectedBar, null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1343,18 +1341,12 @@ export const queueMethods = {
|
||||
}
|
||||
: null;
|
||||
const selectedContextAttrs = selectedContext
|
||||
? ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(selectedContext)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="Right-click to open containing folder"`
|
||||
? this.getContextMenuAttrs(selectedContext, 'Right-click to open containing folder')
|
||||
: '';
|
||||
if (selectedContext) {
|
||||
selectedBar.dataset.model = encodeURIComponent(JSON.stringify(selectedContext));
|
||||
selectedBar.dataset.tooltip = 'Right-click to open containing folder';
|
||||
selectedBar.oncontextmenu = (event) => {
|
||||
window.MLOpenContextMenu?.(event, selectedBar);
|
||||
};
|
||||
this.setContextMenuElement(selectedBar, selectedContext, 'Right-click to open containing folder');
|
||||
} else {
|
||||
selectedBar.removeAttribute('data-model');
|
||||
selectedBar.removeAttribute('data-tooltip');
|
||||
selectedBar.oncontextmenu = null;
|
||||
this.setContextMenuElement(selectedBar, null);
|
||||
}
|
||||
const applyBtnId = `selected-apply-${domKey}`;
|
||||
const removeBtnId = `selected-remove-${domKey}`;
|
||||
|
||||
@@ -641,17 +641,11 @@ export const downloadTargetMethods = {
|
||||
setDownloadFolderContextTarget(element, contextMenuModel = null, tooltip = 'Right-click to open this folder') {
|
||||
if (!element) return;
|
||||
if (contextMenuModel) {
|
||||
element.dataset.model = encodeURIComponent(JSON.stringify(contextMenuModel));
|
||||
element.dataset.tooltip = tooltip;
|
||||
this.setContextMenuElement(element, contextMenuModel, tooltip);
|
||||
element.classList.add('mr-download-folder-context');
|
||||
element.oncontextmenu = (event) => {
|
||||
window.MLOpenContextMenu?.(event, element);
|
||||
};
|
||||
} else {
|
||||
delete element.dataset.model;
|
||||
delete element.dataset.tooltip;
|
||||
this.setContextMenuElement(element, null);
|
||||
element.classList.remove('mr-download-folder-context');
|
||||
element.oncontextmenu = null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1995,7 +1989,7 @@ export const downloadTargetMethods = {
|
||||
...context,
|
||||
open_folder_label: 'Open Folder'
|
||||
};
|
||||
return ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(menuContext)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="${this.escapeHtml(tooltip)}"`;
|
||||
return this.getContextMenuAttrs(menuContext, tooltip);
|
||||
};
|
||||
|
||||
const buildFolderTree = (entries = []) => {
|
||||
|
||||
@@ -1771,7 +1771,7 @@ export const searchPanelMethods = {
|
||||
<tr>
|
||||
<td>${sourcePill}</td>
|
||||
<td>
|
||||
<div class="mr-search-result-model" data-tooltip="${modelTitle}" ${detailsData ? `data-model="${this.escapeHtml(detailsData)}" oncontextmenu="window.MLOpenContextMenu(event, this)"` : ''}>
|
||||
<div class="mr-search-result-model" data-tooltip="${modelTitle}"${this.getContextMenuAttrs(detailsContext)}>
|
||||
<span>${modelHtml}</span>
|
||||
${secondary || filename ? `<small>${secondary || filename}</small>` : ''}
|
||||
</div>
|
||||
@@ -1841,13 +1841,8 @@ export const searchPanelMethods = {
|
||||
} = {}) {
|
||||
const safePercent = Math.max(0, Math.min(100, Number(percent) || 0));
|
||||
const actionAttr = actionDataAttr ? ` ${actionDataAttr}` : '';
|
||||
const contextMenuData = contextMenuModel
|
||||
? this.escapeHtml(encodeURIComponent(JSON.stringify(contextMenuModel)))
|
||||
: '';
|
||||
const contextMenuAttrs = contextMenuData
|
||||
? ` data-model="${contextMenuData}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="${this.escapeHtml(contextMenuTooltip)}"`
|
||||
: '';
|
||||
const containerClass = contextMenuData
|
||||
const contextMenuAttrs = this.getContextMenuAttrs(contextMenuModel, contextMenuTooltip);
|
||||
const containerClass = contextMenuAttrs
|
||||
? 'mr-progress-container mr-download-folder-context'
|
||||
: 'mr-progress-container';
|
||||
const actionHtml = actionClass || actionText
|
||||
@@ -1931,9 +1926,8 @@ export const searchPanelMethods = {
|
||||
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 || '');
|
||||
const modelData = encodeURIComponent(JSON.stringify(contextModel));
|
||||
|
||||
html += `<div class="mr-match-row ${isBestMatch ? 'mr-best-match' : ''}" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">`;
|
||||
html += `<div class="mr-match-row ${isBestMatch ? 'mr-best-match' : ''}"${this.getContextMenuAttrs(contextModel)}>`;
|
||||
html += this.getConfidenceBadge(match.confidence);
|
||||
html += `<span class="mr-match-filename" data-tooltip="${this.escapeHtml(matchPath)}">${this.escapeHtml(matchPath)}</span>`;
|
||||
html += `<span class="mr-match-status ${match.confidence === 100 ? 'mr-match-status-exact' : 'mr-match-status-partial'}">${match.confidence === 100 ? 'Exact' : 'Partial'}</span>`;
|
||||
@@ -1957,9 +1951,8 @@ export const searchPanelMethods = {
|
||||
const match = otherMatches[mIdx];
|
||||
const altBtnId = `resolve-alt-${missingIndex}-${missing.node_id}-${missing.widget_index}-${mIdx}`;
|
||||
const contextModel = this.buildContextMenuModelData(match.model || {}, match.filename || '');
|
||||
const modelData = encodeURIComponent(JSON.stringify(contextModel));
|
||||
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
|
||||
html += `<div class="mr-match-row" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">`;
|
||||
html += `<div class="mr-match-row"${this.getContextMenuAttrs(contextModel)}>`;
|
||||
html += this.getConfidenceBadge(match.confidence);
|
||||
html += `<span class="mr-match-filename" data-tooltip="${this.escapeHtml(matchPath)}">${this.escapeHtml(matchPath)}</span>`;
|
||||
html += `<span class="mr-match-status mr-match-status-partial">Partial</span>`;
|
||||
|
||||
@@ -3,6 +3,59 @@ import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
export const renderFormatMethods = {
|
||||
encodeContextMenuModel(context = null) {
|
||||
if (!context) return '';
|
||||
try {
|
||||
return encodeURIComponent(JSON.stringify(context));
|
||||
} catch (error) {
|
||||
console.warn('Model Resolver: failed to encode context menu model', error);
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
getContextMenuAttrs(context = null, tooltip = '') {
|
||||
const data = this.encodeContextMenuModel(context);
|
||||
if (!data) return '';
|
||||
const tooltipAttr = tooltip ? ` data-tooltip="${this.escapeHtml(tooltip)}"` : '';
|
||||
return ` data-model="${this.escapeHtml(data)}" oncontextmenu="window.MLOpenContextMenu(event, this)"${tooltipAttr}`;
|
||||
},
|
||||
|
||||
getContextMenuProps(context = null, tooltip = '') {
|
||||
const data = this.encodeContextMenuModel(context);
|
||||
if (!data) return {};
|
||||
const props = {
|
||||
"data-model": data,
|
||||
oncontextmenu: (event) => {
|
||||
window.MLOpenContextMenu?.(event, event.currentTarget);
|
||||
}
|
||||
};
|
||||
if (tooltip) {
|
||||
props["data-tooltip"] = tooltip;
|
||||
}
|
||||
return props;
|
||||
},
|
||||
|
||||
setContextMenuElement(element, context = null, tooltip = '') {
|
||||
if (!element) return;
|
||||
const data = this.encodeContextMenuModel(context);
|
||||
if (!data) {
|
||||
element.removeAttribute('data-model');
|
||||
element.removeAttribute('data-tooltip');
|
||||
element.oncontextmenu = null;
|
||||
return;
|
||||
}
|
||||
|
||||
element.dataset.model = data;
|
||||
if (tooltip) {
|
||||
element.dataset.tooltip = tooltip;
|
||||
} else {
|
||||
element.removeAttribute('data-tooltip');
|
||||
}
|
||||
element.oncontextmenu = (event) => {
|
||||
window.MLOpenContextMenu?.(event, event.currentTarget);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a colored confidence badge HTML
|
||||
* @param {number} confidence - Confidence percentage (0-100)
|
||||
@@ -110,13 +163,8 @@ export const renderFormatMethods = {
|
||||
const icon = icons[type] || icons.info;
|
||||
const contextMenuModel = options?.contextMenuModel || null;
|
||||
const contextMenuTooltip = options?.contextMenuTooltip || 'Right-click to open download folder';
|
||||
const contextMenuData = contextMenuModel
|
||||
? this.escapeHtml(encodeURIComponent(JSON.stringify(contextMenuModel)))
|
||||
: '';
|
||||
const contextMenuAttrs = contextMenuData
|
||||
? ` data-model="${contextMenuData}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="${this.escapeHtml(contextMenuTooltip)}"`
|
||||
: '';
|
||||
const className = contextMenuData
|
||||
const contextMenuAttrs = this.getContextMenuAttrs(contextMenuModel, contextMenuTooltip);
|
||||
const className = contextMenuAttrs
|
||||
? `mr-status mr-status-${type} mr-download-folder-context`
|
||||
: `mr-status mr-status-${type}`;
|
||||
|
||||
|
||||
@@ -881,8 +881,7 @@ export const missingBrowserMethods = {
|
||||
};
|
||||
};
|
||||
const getLocalPathContextAttrs = (context, tooltip = 'Right-click for options') => {
|
||||
if (!context) return '';
|
||||
return ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(context)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="${this.escapeHtml(tooltip)}"`;
|
||||
return this.getContextMenuAttrs(context, tooltip);
|
||||
};
|
||||
const makeModelEntry = (model, index, preferredCategory = getPreferredModelCategory()) => {
|
||||
const category = getModelCategory(model);
|
||||
@@ -1542,10 +1541,7 @@ export const missingBrowserMethods = {
|
||||
};
|
||||
|
||||
if (contextMenuModel) {
|
||||
notificationProps["data-model"] = encodeURIComponent(JSON.stringify(contextMenuModel));
|
||||
notificationProps.oncontextmenu = (event) => {
|
||||
window.MLOpenContextMenu?.(event, event.currentTarget);
|
||||
};
|
||||
Object.assign(notificationProps, this.getContextMenuProps?.(contextMenuModel) || {});
|
||||
}
|
||||
|
||||
if (type === 'success') {
|
||||
|
||||
@@ -262,8 +262,7 @@ export const tabsLoadedMethods = {
|
||||
const fullName = model.name || model.original_path?.split(/[\/\\]/).pop() || 'Unknown';
|
||||
const name = fullName.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
|
||||
const strength = model.strength !== null && model.strength !== undefined ? model.strength.toFixed(2) : null;
|
||||
const modelData = encodeURIComponent(JSON.stringify(model));
|
||||
html += `<span class="mr-model-chip" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">${this.escapeHtml(name)}${strength !== null ? `<span class="mr-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
html += `<span class="mr-model-chip"${this.getContextMenuAttrs(model)}>${this.escapeHtml(name)}${strength !== null ? `<span class="mr-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
}
|
||||
html += `</div></div>`;
|
||||
}
|
||||
@@ -282,8 +281,7 @@ export const tabsLoadedMethods = {
|
||||
const fullName = model.name || model.original_path?.split(/[\/\\]/).pop() || 'Unknown';
|
||||
const name = fullName.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
|
||||
const strength = model.strength !== null && model.strength !== undefined ? model.strength.toFixed(2) : null;
|
||||
const modelData = encodeURIComponent(JSON.stringify(model));
|
||||
html += `<span class="mr-model-chip" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">${this.escapeHtml(name)}${strength !== null ? `<span class="mr-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
html += `<span class="mr-model-chip"${this.getContextMenuAttrs(model)}>${this.escapeHtml(name)}${strength !== null ? `<span class="mr-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
}
|
||||
html += `</div></div>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user