Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
api.txt
test.mjs
test.mjsCRUSH.md
7 changes: 7 additions & 0 deletions preferences.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
108 changes: 105 additions & 3 deletions tab_sort_clear.uc.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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");
Expand All @@ -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: {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
`<menuseparator id="context_zen-sort-tabs-separator"/>
<menuitem id="context_zenSortTabs"
label="Sort Tabs"
tooltiptext="Sort tabs in current workspace into groups by topic (AI)"
command="cmd_zenSortTabs"/>`
);

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

Expand Down Expand Up @@ -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();
}
}


Expand Down Expand Up @@ -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...`);
Expand Down Expand Up @@ -1692,6 +1792,7 @@
console.error("INIT Status:", {
gBrowserReady,
commandSetExists,
tabContextMenuExists,
separatorExists,
peripheryExists,
gZenWorkspacesReady
Expand All @@ -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);
Expand Down