From d3c7ace464a3313f3b60350bfb400377613c88d8 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Wed, 24 Jun 2026 22:57:56 +0200 Subject: [PATCH] feat: add folder browser context menu and local model deduplication - Add right-click 'Open Folder' context menu to folder browser rows and root group headers, with left-click guards to prevent context actions on non-primary clicks - Deduplicate local model entries before filtering using path-based identity and category ranking to prefer the active download category --- .../search/download_target_methods.js | 21 ++++++-- web/resolver/views/missing_browser_methods.js | 51 ++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/web/resolver/search/download_target_methods.js b/web/resolver/search/download_target_methods.js index 25dce73..ff1d907 100644 --- a/web/resolver/search/download_target_methods.js +++ b/web/resolver/search/download_target_methods.js @@ -2083,6 +2083,15 @@ export const downloadTargetMethods = { if (!clean) return 'Default root'; return clean.split(/[\\\/]+/).filter(Boolean).pop() || clean; }; + const getFolderBrowserContextAttrs = (category = '', subfolder = '', baseDirectory = '', tooltip = 'Right-click to open this folder') => { + const context = this.getDownloadTargetFolderContext(category, subfolder, baseDirectory); + if (!context) return ''; + const menuContext = { + ...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)}"`; + }; const buildFolderTree = (entries = []) => { const root = new Map(); @@ -2124,6 +2133,7 @@ export const downloadTargetMethods = { const renderFolderTreeNodes = ( nodeMap, rootGroup, + category, expandedSet, filter, selectedValue, @@ -2137,6 +2147,7 @@ export const downloadTargetMethods = { const childCount = countTreeNodes(node.children); const nodeValue = node.entry?.value || node.path; const nodeBaseDirectory = node.entry?.baseDirectory || rootGroup.baseDirectory || ''; + const contextAttrs = getFolderBrowserContextAttrs(category, nodeValue, nodeBaseDirectory); const isSelected = normalizeSubfolderPath(nodeValue).toLowerCase() === selectedValue.toLowerCase() && String(nodeBaseDirectory || '') === String(selectedBaseDirectory || ''); const shouldExpand = hasChildren && ( @@ -2148,7 +2159,7 @@ export const downloadTargetMethods = { ? `` : ``; const children = hasChildren - ? `
${renderFolderTreeNodes(node.children, rootGroup, expandedSet, filter, selectedValue, selectedBaseDirectory)}
` + ? `
${renderFolderTreeNodes(node.children, rootGroup, category, expandedSet, filter, selectedValue, selectedBaseDirectory)}
` : ''; const rowClass = [ 'mr-folder-browser-row', @@ -2161,7 +2172,7 @@ export const downloadTargetMethods = { return `
-
+
${toggle} ${getSvgIcon('folderOpen', 'currentColor', 'mr-folder-browser-svg')} ${this.escapeHtml(node.name)} @@ -2235,15 +2246,17 @@ export const downloadTargetMethods = { const treeHtml = renderFolderTreeNodes( tree, group, + category, expandedSet, normalizedFilter, selectedValue, selectedBaseDirectory ); + const rootContextAttrs = getFolderBrowserContextAttrs(category, '', group.baseDirectory); return `
-