From 60cc3d320f3998ad94d525b309b4f001f009177f Mon Sep 17 00:00:00 2001 From: yetris <197530004+yetris@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:24:07 -0700 Subject: [PATCH 1/7] Create manifest.json --- submissions/Open all this Tabs/manifest.json | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 submissions/Open all this Tabs/manifest.json diff --git a/submissions/Open all this Tabs/manifest.json b/submissions/Open all this Tabs/manifest.json new file mode 100644 index 00000000..1c5d8aca --- /dev/null +++ b/submissions/Open all this Tabs/manifest.json @@ -0,0 +1,27 @@ +{ + "manifest_version": 3, + "name": "Multi Tab Opener", + "version": "1.0", + "description": "Open multiple tabs with one click", + "permissions": [ + "tabs", + "activeTab" + ], + "action": { + "default_popup": "popup.html", + "default_title": "Open Multiple Tabs" + }, + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "background": { + "service_worker": "background.js" + }, + "browser_specific_settings": { + "gecko": { + "id": "multi-tab-opener@example.com" + } + } +} From fd543b0035103f68d2d9cf96830de74679ba2eb5 Mon Sep 17 00:00:00 2001 From: yetris <197530004+yetris@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:25:38 -0700 Subject: [PATCH 2/7] Create popup.html --- submissions/Open all this Tabs/popup.html | 142 ++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 submissions/Open all this Tabs/popup.html diff --git a/submissions/Open all this Tabs/popup.html b/submissions/Open all this Tabs/popup.html new file mode 100644 index 00000000..5bcf4e4a --- /dev/null +++ b/submissions/Open all this Tabs/popup.html @@ -0,0 +1,142 @@ + + + + + + + +
+

Multi Tab Opener

+ +
+ + +
+ + + +
+
+ + + + From db9af1a99112de14514feebbdff573e2a7f80a5b Mon Sep 17 00:00:00 2001 From: yetris <197530004+yetris@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:26:42 -0700 Subject: [PATCH 3/7] Create popup.js --- submissions/Open all this Tabs/popup.js | 197 ++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 submissions/Open all this Tabs/popup.js diff --git a/submissions/Open all this Tabs/popup.js b/submissions/Open all this Tabs/popup.js new file mode 100644 index 00000000..84626361 --- /dev/null +++ b/submissions/Open all this Tabs/popup.js @@ -0,0 +1,197 @@ +// Cross-browser compatibility +const isFirefox = typeof browser !== 'undefined'; +const browserAPI = isFirefox ? browser : chrome; + +// Default URLs to open +const defaultUrls = [ + 'https://www.google.com', + 'https://www.github.com', + 'https://www.stackoverflow.com', + 'https://www.youtube.com', + 'https://www.wikipedia.org' +]; + +// Custom URLs storage +let customUrls = []; + +// Load custom URLs from storage +function loadCustomUrls() { + if (isFirefox) { + browser.storage.local.get('customUrls').then(result => { + customUrls = result.customUrls || []; + updateUrlList(); + }); + } else { + chrome.storage.local.get('customUrls', (result) => { + customUrls = result.customUrls || []; + updateUrlList(); + }); + } +} + +// Save custom URLs to storage +function saveCustomUrls() { + const data = { customUrls: customUrls }; + if (isFirefox) { + browser.storage.local.set(data); + } else { + chrome.storage.local.set(data); + } +} + +// Update URL list display +function updateUrlList() { + const urlList = document.getElementById('urlList'); + urlList.innerHTML = ''; + + customUrls.forEach((url, index) => { + const urlItem = document.createElement('div'); + urlItem.className = 'url-item'; + urlItem.innerHTML = ` + ${url} + + `; + urlList.appendChild(urlItem); + }); + + // Add remove listeners + document.querySelectorAll('.remove-url').forEach(btn => { + btn.addEventListener('click', (e) => { + const index = parseInt(e.target.dataset.index); + customUrls.splice(index, 1); + saveCustomUrls(); + updateUrlList(); + }); + }); +} + +// Validate URL +function isValidUrl(string) { + try { + const url = new URL(string); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch (_) { + return false; + } +} + +// Add URL to custom list +function addCustomUrl() { + const urlInput = document.getElementById('urlInput'); + let url = urlInput.value.trim(); + + if (!url) { + showStatus('Please enter a URL', 'error'); + return; + } + + // Add protocol if missing + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = 'https://' + url; + } + + if (!isValidUrl(url)) { + showStatus('Please enter a valid URL', 'error'); + return; + } + + if (customUrls.includes(url)) { + showStatus('URL already exists', 'error'); + return; + } + + customUrls.push(url); + saveCustomUrls(); + updateUrlList(); + urlInput.value = ''; + showStatus('URL added successfully', 'success'); +} + +// Show status message +function showStatus(message, type = 'info') { + const status = document.getElementById('status'); + status.textContent = message; + status.style.color = type === 'error' ? '#ff6b6b' : type === 'success' ? '#51cf66' : 'white'; + + setTimeout(() => { + status.textContent = ''; + }, 3000); +} + +// Open tabs function +async function openTabs(urls) { + if (!urls || urls.length === 0) { + showStatus('No URLs to open', 'error'); + return; + } + + try { + let successCount = 0; + + for (const url of urls) { + try { + if (isFirefox) { + await browser.tabs.create({ url: url, active: false }); + } else { + await new Promise((resolve) => { + chrome.tabs.create({ url: url, active: false }, resolve); + }); + } + successCount++; + } catch (error) { + console.error('Error opening tab:', url, error); + } + } + + showStatus(`Opened ${successCount} of ${urls.length} tabs`, 'success'); + + // Close popup after a short delay + setTimeout(() => { + window.close(); + }, 1500); + + } catch (error) { + console.error('Error opening tabs:', error); + showStatus('Error opening tabs. Check permissions.', 'error'); + } +} + +// Toggle custom section +function toggleCustomSection() { + const customSection = document.getElementById('customSection'); + const isVisible = customSection.style.display !== 'none'; + customSection.style.display = isVisible ? 'none' : 'block'; + + if (!isVisible) { + loadCustomUrls(); + } +} + +// Event listeners +document.addEventListener('DOMContentLoaded', () => { + // Open predefined tabs + document.getElementById('openPredefined').addEventListener('click', () => { + openTabs(defaultUrls); + }); + + // Toggle custom tabs section + document.getElementById('openCustom').addEventListener('click', toggleCustomSection); + + // Add custom URL + document.getElementById('addUrl').addEventListener('click', addCustomUrl); + + // Enter key to add URL + document.getElementById('urlInput').addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + addCustomUrl(); + } + }); + + // Load initial data + loadCustomUrls(); +}); + +// Handle popup unload +window.addEventListener('beforeunload', () => { + saveCustomUrls(); +}); From b1671f2003c7df4dacb812a3d9542a4f2430b17d Mon Sep 17 00:00:00 2001 From: yetris <197530004+yetris@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:28:21 -0700 Subject: [PATCH 4/7] Create background.js --- submissions/Open all this Tabs/background.js | 113 +++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 submissions/Open all this Tabs/background.js diff --git a/submissions/Open all this Tabs/background.js b/submissions/Open all this Tabs/background.js new file mode 100644 index 00000000..6d208d47 --- /dev/null +++ b/submissions/Open all this Tabs/background.js @@ -0,0 +1,113 @@ +// Background script for Chrome Extension +// Cross-browser compatibility +const isFirefox = typeof browser !== 'undefined'; +const browserAPI = isFirefox ? browser : chrome; + +// Handle extension installation +browserAPI.runtime.onInstalled.addListener((details) => { + console.log('Multi Tab Opener extension installed/updated'); + + if (details.reason === 'install') { + // Set default custom URLs on first install + const defaultCustomUrls = []; + browserAPI.storage.local.set({ customUrls: defaultCustomUrls }); + } +}); + +// Handle messages from popup or content scripts +browserAPI.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === 'openTabs') { + openMultipleTabs(request.urls).then(result => { + sendResponse({ success: true, count: result }); + }).catch(error => { + console.error('Error opening tabs:', error); + sendResponse({ success: false, error: error.message }); + }); + return true; // Will respond asynchronously + } +}); + +// Function to open multiple tabs +async function openMultipleTabs(urls) { + if (!urls || !Array.isArray(urls) || urls.length === 0) { + throw new Error('No valid URLs provided'); + } + + let successCount = 0; + const errors = []; + + for (const url of urls) { + try { + // Validate URL + new URL(url); // This will throw if URL is invalid + + // Create new tab + await browserAPI.tabs.create({ + url: url, + active: false // Don't make the new tab active + }); + + successCount++; + } catch (error) { + console.error(`Failed to open tab for URL: ${url}`, error); + errors.push({ url, error: error.message }); + } + } + + if (errors.length > 0) { + console.warn('Some tabs failed to open:', errors); + } + + return successCount; +} + +// Handle browser action click (fallback for browsers that don't support popups) +if (browserAPI.action && browserAPI.action.onClicked) { + browserAPI.action.onClicked.addListener(async (tab) => { + // This is a fallback in case popup fails to load + console.log('Browser action clicked, opening default tabs'); + + const defaultUrls = [ + 'https://www.google.com', + 'https://www.github.com', + 'https://www.stackoverflow.com', + 'https://www.youtube.com', + 'https://www.wikipedia.org' + ]; + + try { + await openMultipleTabs(defaultUrls); + } catch (error) { + console.error('Error in fallback tab opening:', error); + } + }); +} + +// Handle context menu (optional feature) +if (browserAPI.contextMenus) { + browserAPI.runtime.onInstalled.addListener(() => { + browserAPI.contextMenus.create({ + id: 'openMultipleTabs', + title: 'Open Multiple Tabs', + contexts: ['page', 'selection'] + }); + }); + + browserAPI.contextMenus.onClicked.addListener(async (info, tab) => { + if (info.menuItemId === 'openMultipleTabs') { + const defaultUrls = [ + 'https://www.google.com', + 'https://www.github.com', + 'https://www.stackoverflow.com', + 'https://www.youtube.com', + 'https://www.wikipedia.org' + ]; + + try { + await openMultipleTabs(defaultUrls); + } catch (error) { + console.error('Error opening tabs from context menu:', error); + } + } + }); +} From 931230df3faacff7633292eb496c490034ec7e74 Mon Sep 17 00:00:00 2001 From: yetris <197530004+yetris@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:29:35 -0700 Subject: [PATCH 5/7] Create manifest-firefox.json --- .../Open all this Tabs/manifest-firefox.json | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 submissions/Open all this Tabs/manifest-firefox.json diff --git a/submissions/Open all this Tabs/manifest-firefox.json b/submissions/Open all this Tabs/manifest-firefox.json new file mode 100644 index 00000000..468fd9c0 --- /dev/null +++ b/submissions/Open all this Tabs/manifest-firefox.json @@ -0,0 +1,30 @@ +{ + "manifest_version": 2, + "name": "Multi Tab Opener", + "version": "1.0", + "description": "Open multiple tabs with one click", + "permissions": [ + "tabs", + "activeTab", + "storage" + ], + "browser_action": { + "default_popup": "popup.html", + "default_title": "Open Multiple Tabs" + }, + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "background": { + "scripts": ["background-firefox.js"], + "persistent": false + }, + "browser_specific_settings": { + "gecko": { + "id": "multi-tab-opener@example.com", + "strict_min_version": "57.0" + } + } +} From bd221e222e24ab0135692872a822c8e1621ebe42 Mon Sep 17 00:00:00 2001 From: yetris <197530004+yetris@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:30:53 -0700 Subject: [PATCH 6/7] Create background-firefox.js --- .../Open all this Tabs/background-firefox.js | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 submissions/Open all this Tabs/background-firefox.js diff --git a/submissions/Open all this Tabs/background-firefox.js b/submissions/Open all this Tabs/background-firefox.js new file mode 100644 index 00000000..8e4187ec --- /dev/null +++ b/submissions/Open all this Tabs/background-firefox.js @@ -0,0 +1,104 @@ +// Background script for Firefox +// Handle extension installation +browser.runtime.onInstalled.addListener((details) => { + console.log('Multi Tab Opener extension installed/updated'); + + if (details.reason === 'install') { + // Set default custom URLs on first install + const defaultCustomUrls = []; + browser.storage.local.set({ customUrls: defaultCustomUrls }); + } +}); + +// Handle messages from popup or content scripts +browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === 'openTabs') { + openMultipleTabs(request.urls).then(result => { + sendResponse({ success: true, count: result }); + }).catch(error => { + console.error('Error opening tabs:', error); + sendResponse({ success: false, error: error.message }); + }); + return true; // Will respond asynchronously + } +}); + +// Function to open multiple tabs +async function openMultipleTabs(urls) { + if (!urls || !Array.isArray(urls) || urls.length === 0) { + throw new Error('No valid URLs provided'); + } + + let successCount = 0; + const errors = []; + + for (const url of urls) { + try { + // Validate URL + new URL(url); // This will throw if URL is invalid + + // Create new tab + await browser.tabs.create({ + url: url, + active: false // Don't make the new tab active + }); + + successCount++; + } catch (error) { + console.error(`Failed to open tab for URL: ${url}`, error); + errors.push({ url, error: error.message }); + } + } + + if (errors.length > 0) { + console.warn('Some tabs failed to open:', errors); + } + + return successCount; +} + +// Handle browser action click (fallback) +browser.browserAction.onClicked.addListener(async (tab) => { + console.log('Browser action clicked, opening default tabs'); + + const defaultUrls = [ + 'https://www.google.com', + 'https://www.github.com', + 'https://www.stackoverflow.com', + 'https://www.youtube.com', + 'https://www.wikipedia.org' + ]; + + try { + await openMultipleTabs(defaultUrls); + } catch (error) { + console.error('Error in fallback tab opening:', error); + } +}); + +// Handle context menu +browser.runtime.onInstalled.addListener(() => { + browser.contextMenus.create({ + id: 'openMultipleTabs', + title: 'Open Multiple Tabs', + contexts: ['page', 'selection'] + }); +}); + +browser.contextMenus.onClicked.addListener(async (info, tab) => { + if (info.menuItemId === 'openMultipleTabs') { + const defaultUrls = [ + 'https://www.google.com', + 'https://www.github.com', + 'https://www.stackoverflow.com', + 'https://www.youtube.com', + 'https://www.wikipedia.org' + ]; + + try { + await openMultipleTabs(defaultUrls); + } catch (error) { + console.error('Error opening tabs from context menu:', error); + } + } +}); From ff818797395447976559fbd82883c8e31f2cd7a7 Mon Sep 17 00:00:00 2001 From: yetris <197530004+yetris@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:36:25 -0700 Subject: [PATCH 7/7] Create readme.md --- submissions/Open all this Tabs/icons/readme.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 submissions/Open all this Tabs/icons/readme.md diff --git a/submissions/Open all this Tabs/icons/readme.md b/submissions/Open all this Tabs/icons/readme.md new file mode 100644 index 00000000..ef625f89 --- /dev/null +++ b/submissions/Open all this Tabs/icons/readme.md @@ -0,0 +1 @@ +Open all this Tabs icons