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);