From fb8a65758bb260abeda2bb744768117ab7a275c8 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Wed, 24 Jun 2026 23:19:02 +0200 Subject: [PATCH] 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. --- web/resolver/actions/queue_methods.js | 20 ++---- .../search/download_target_methods.js | 12 +--- web/resolver/search/search_panel.js | 17 ++--- web/resolver/utils/render_format_methods.js | 62 ++++++++++++++++--- web/resolver/views/missing_browser_methods.js | 8 +-- web/resolver/views/tabs_loaded_methods.js | 6 +- 6 files changed, 73 insertions(+), 52 deletions(-) diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js index 98b8012..919af32 100644 --- a/web/resolver/actions/queue_methods.js +++ b/web/resolver/actions/queue_methods.js @@ -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 += `
`; @@ -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}`; diff --git a/web/resolver/search/download_target_methods.js b/web/resolver/search/download_target_methods.js index ef8a499..6b79f58 100644 --- a/web/resolver/search/download_target_methods.js +++ b/web/resolver/search/download_target_methods.js @@ -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 = []) => { diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index a88f339..3a6d44d 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -1771,7 +1771,7 @@ export const searchPanelMethods = { ${sourcePill} -
+
${modelHtml} ${secondary || filename ? `${secondary || filename}` : ''}
@@ -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 += `
`; + html += `
`; html += this.getConfidenceBadge(match.confidence); html += `${this.escapeHtml(matchPath)}`; html += `${match.confidence === 100 ? 'Exact' : 'Partial'}`; @@ -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 += `
`; + html += `
`; html += this.getConfidenceBadge(match.confidence); html += `${this.escapeHtml(matchPath)}`; html += `Partial`; diff --git a/web/resolver/utils/render_format_methods.js b/web/resolver/utils/render_format_methods.js index 63f18b4..a19e1fb 100644 --- a/web/resolver/utils/render_format_methods.js +++ b/web/resolver/utils/render_format_methods.js @@ -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}`; diff --git a/web/resolver/views/missing_browser_methods.js b/web/resolver/views/missing_browser_methods.js index c75f8b5..60bac02 100644 --- a/web/resolver/views/missing_browser_methods.js +++ b/web/resolver/views/missing_browser_methods.js @@ -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') { diff --git a/web/resolver/views/tabs_loaded_methods.js b/web/resolver/views/tabs_loaded_methods.js index 0a25b2a..372ace0 100644 --- a/web/resolver/views/tabs_loaded_methods.js +++ b/web/resolver/views/tabs_loaded_methods.js @@ -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 += `${this.escapeHtml(name)}${strength !== null ? `${this.escapeHtml(strength)}` : ''}`; + html += `${this.escapeHtml(name)}${strength !== null ? `${this.escapeHtml(strength)}` : ''}`; } html += `
`; } @@ -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 += `${this.escapeHtml(name)}${strength !== null ? `${this.escapeHtml(strength)}` : ''}`; + html += `${this.escapeHtml(name)}${strength !== null ? `${this.escapeHtml(strength)}` : ''}`; } html += `
`; }