diff --git a/.gitignore b/.gitignore index 2833cd3..7d5d4a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ api.txt -test.mjs \ No newline at end of file +test.mjsCRUSH.md diff --git a/preferences.json b/preferences.json index 314f50d..a9aa564 100644 --- a/preferences.json +++ b/preferences.json @@ -16,6 +16,13 @@ "type": "checkbox", "defaultValue": true }, + { + "property": "extensions.tabgroups.enable_context_menu", + "label": "Enable the sort tabs context menu item.", + "type": "checkbox", + "defaultValue": true, + "restart": true + }, { "property": "extensions.tabgroups.ai_model", "label": "Choose AI - Gemini, Ollama, or Mistral", diff --git a/tab_sort_clear.uc.js b/tab_sort_clear.uc.js index 2a82617..c80d227 100644 --- a/tab_sort_clear.uc.js +++ b/tab_sort_clear.uc.js @@ -1,10 +1,11 @@ -// VERSION 4.11.0 (Added Mistral API support) +// VERSION 4.12.1 (Added Sort Tabs to tab context menu) (() => { // --- Configuration --- // Feature toggle preference keys const ENABLE_SORT_PREF = "extensions.tabgroups.enable_sort"; const ENABLE_CLEAR_PREF = "extensions.tabgroups.enable_clear"; + const ENABLE_CONTEXT_MENU_PREF = "extensions.tabgroups.enable_context_menu"; // Preference Key for AI Model Selection const AI_MODEL_PREF = "extensions.tabgroups.ai_model"; // '1' for Gemini, '2' for Ollama, '3' for Mistral // Preference Keys for AI Config @@ -38,6 +39,7 @@ // Read preference values const ENABLE_SORT_VALUE = getPref(ENABLE_SORT_PREF, true); const ENABLE_CLEAR_VALUE = getPref(ENABLE_CLEAR_PREF, true); + const ENABLE_CONTEXT_MENU_VALUE = getPref(ENABLE_CONTEXT_MENU_PREF, true); const AI_MODEL_VALUE = getPref(AI_MODEL_PREF, "1"); // Default to Gemini const OLLAMA_ENDPOINT_VALUE = getPref(OLLAMA_ENDPOINT_PREF, "http://localhost:11434/api/generate"); const OLLAMA_MODEL_VALUE = getPref(OLLAMA_MODEL_PREF, "llama3.2"); @@ -49,7 +51,8 @@ const CONFIG = { featureConfig: { sort: ENABLE_SORT_VALUE, - clear: ENABLE_CLEAR_VALUE + clear: ENABLE_CLEAR_VALUE, + contextMenu: ENABLE_CONTEXT_MENU_VALUE }, apiConfig: { ollama: { @@ -266,6 +269,17 @@ } } + @media not (-moz-bool-pref: "${ENABLE_CONTEXT_MENU_PREF}") { + + #context_zenSortTabs { + display: none; + } + + #context_zen-sort-tabs-separator { + display: none; + } + } + /*======== sort-button , clear-button ============*/ .pinned-tabs-container-separator{ height: 100% !important; @@ -1496,6 +1510,85 @@ // --- Button Initialization & Workspace Handling --- + function addContextMenuItem() { + const tabContextMenu = document.getElementById('tabContextMenu'); + if (!tabContextMenu) { + console.error("BUTTONS: Could not find 'tabContextMenu'. Context menu item not added."); + return; + } + + // Check if our menu item already exists + if (tabContextMenu.querySelector('#context_zenSortTabs')) { + console.log("BUTTONS: Context menu item already exists."); + return; + } + + try { + const menuFragment = window.MozXULElement.parseXULToFragment( + ` + ` + ); + + // Insert before the "Reload Tab" menu item for logical grouping + const reloadTabItem = tabContextMenu.querySelector('#context_reloadTab'); + if (reloadTabItem) { + reloadTabItem.before(menuFragment); + } else { + // Fallback: append to the end + tabContextMenu.appendChild(menuFragment); + } + + console.log("BUTTONS: Sort Tabs context menu item added successfully."); + } catch (e) { + console.error("BUTTONS: Error adding context menu item:", e); + } + } + + function setupContextMenuListener() { + const tabContextMenu = document.getElementById('tabContextMenu'); + if (!tabContextMenu) { + console.error("BUTTONS: Could not find 'tabContextMenu' for listener setup."); + return; + } + + // Add popupshowing listener to control visibility + tabContextMenu.addEventListener('popupshowing', (event) => { + try { + const menuItem = document.getElementById('context_zenSortTabs'); + const separator = document.getElementById('context_zen-sort-tabs-separator'); + + if (!menuItem || !separator) return; + + // Check if sort feature and context menu are enabled + if (!CONFIG.featureConfig.sort || !CONFIG.featureConfig.contextMenu) { + menuItem.setAttribute('hidden', 'true'); + separator.setAttribute('hidden', 'true'); + return; + } + + // Check if we're in the correct workspace + const currentWorkspaceId = window.gZenWorkspaces?.activeWorkspace; + const showMenuItem = currentWorkspaceId && + (!gBrowser?.selectedTabs || gBrowser.selectedTabs.length <= 1); // Hide when multiple tabs selected (use button instead) + + if (showMenuItem) { + menuItem.removeAttribute('hidden'); + separator.removeAttribute('hidden'); + } else { + menuItem.setAttribute('hidden', 'true'); + separator.setAttribute('hidden', 'true'); + } + } catch (e) { + console.error("BUTTONS: Error in context menu popupshowing listener:", e); + } + }); + + console.log("BUTTONS: Context menu listener setup complete."); + } + function ensureButtonsExist(container) { if (!container) return; @@ -1588,6 +1681,12 @@ console.error("BUTTONS INIT: Error adding command listener:", e); } } + + // Setup context menu items and listeners + if (CONFIG.featureConfig.contextMenu) { + addContextMenuItem(); + setupContextMenuListener(); + } } @@ -1657,10 +1756,11 @@ const separatorExists = !!document.querySelector(".pinned-tabs-container-separator"); const peripheryExists = !!document.querySelector('#tabbrowser-arrowscrollbox-periphery'); const commandSetExists = !!document.querySelector("commandset#zenCommandSet"); + const tabContextMenuExists = !!document.getElementById('tabContextMenu'); const gBrowserReady = typeof gBrowser !== 'undefined' && gBrowser.tabContainer; const gZenWorkspacesReady = typeof gZenWorkspaces !== 'undefined' && typeof gZenWorkspaces.activeWorkspace !== 'undefined'; - const ready = gBrowserReady && commandSetExists && (separatorExists || peripheryExists) && gZenWorkspacesReady; + const ready = gBrowserReady && commandSetExists && tabContextMenuExists && (separatorExists || peripheryExists) && gZenWorkspacesReady; if (ready) { console.log(`INIT: Required elements found after ${checkCount} checks. Initializing...`); @@ -1692,6 +1792,7 @@ console.error("INIT Status:", { gBrowserReady, commandSetExists, + tabContextMenuExists, separatorExists, peripheryExists, gZenWorkspacesReady @@ -1700,6 +1801,7 @@ if (!gZenWorkspacesReady) console.error(" -> gZenWorkspaces might not be fully initialized yet (activeWorkspace missing?). Ensure Zen Tab Organizer extension is loaded and enabled BEFORE this script runs."); if (!separatorExists && !peripheryExists) console.error(" -> Neither separator element '.pinned-tabs-container-separator' nor fallback periphery '#tabbrowser-arrowscrollbox-periphery' found in the DOM."); if (!commandSetExists) console.error(" -> Command set '#zenCommandSet' not found. Ensure Zen Tab Organizer extension is loaded and enabled."); + if (!tabContextMenuExists) console.error(" -> Tab context menu '#tabContextMenu' not found. Ensure Zen Browser tab context menu is loaded."); if (!gBrowserReady) console.error(" -> Global 'gBrowser' object not ready."); } }, checkInterval);