From d93670382411283ef42ca015f602f7ea70a827f4 Mon Sep 17 00:00:00 2001 From: Leonardo Fischer Date: Tue, 26 May 2026 11:26:37 -0300 Subject: [PATCH] Primeira versao --- loader.html | 91 ++++++++++++ loader.js | 34 +++++ manifest.json | 17 +++ popup.html | 404 ++++++++++++++++++++++++++++++++++++++++++++++++++ popup.js | 126 ++++++++++++++++ 5 files changed, 672 insertions(+) create mode 100644 loader.html create mode 100644 loader.js create mode 100644 manifest.json create mode 100644 popup.html create mode 100644 popup.js diff --git a/loader.html b/loader.html new file mode 100644 index 0000000..79ba422 --- /dev/null +++ b/loader.html @@ -0,0 +1,91 @@ + + + + + Waiting for focus… + + + +
+

Waiting for focus to load

+

+ + + + + diff --git a/loader.js b/loader.js new file mode 100644 index 0000000..baeec43 --- /dev/null +++ b/loader.js @@ -0,0 +1,34 @@ +(() => { + const params = new URLSearchParams(location.search); + const destUrl = params.get('url'); + + if (!destUrl) { + document.querySelector('.label').textContent = 'No URL provided.'; + document.getElementById('dest').textContent = ''; + document.getElementById('goNow').style.display = 'none'; + return; + } + + document.getElementById('dest').textContent = destUrl; + document.title = '⏳ ' + destUrl; + + let navigated = false; + + function navigate() { + if (navigated) return; + navigated = true; + location.replace(destUrl); + } + + // "Load now" button — immediate bypass + document.getElementById('goNow').addEventListener('click', navigate); + + // Navigate when the tab becomes visible (user switches to it) + if (document.visibilityState === 'visible') { + setTimeout(navigate, 80); + } else { + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') navigate(); + }); + } +})(); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..7d82ec0 --- /dev/null +++ b/manifest.json @@ -0,0 +1,17 @@ +{ + "manifest_version": 3, + "name": "URL Launcher", + "version": "1.0.0", + "description": "Paste a list of URLs and open them all as tabs, with optional lazy-load on focus.", + "action": { + "default_popup": "popup.html", + "default_title": "URL Launcher" + }, + "permissions": ["tabs"], + "web_accessible_resources": [ + { + "resources": ["loader.html", "loader.js"], + "matches": [""] + } + ] +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..6442a15 --- /dev/null +++ b/popup.html @@ -0,0 +1,404 @@ + + + + + + URL Launcher + + + + +
+ + URL Launcher + + lazy + +
+ +
+
+ URLs / Text + +
+ +
+ + 0 URLs +
+ +
+ + + +
+ +
+
+ + + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..0715873 --- /dev/null +++ b/popup.js @@ -0,0 +1,126 @@ +(() => { + const urlInput = document.getElementById('urlInput'); + const lazyLoad = document.getElementById('lazyLoad'); + const openBtn = document.getElementById('openBtn'); + const extractBtn = document.getElementById('extractBtn'); + const urlCount = document.getElementById('urlCount'); + const status = document.getElementById('status'); + const modeBadge = document.getElementById('modeBadgeInner'); + + // ── Helpers ──────────────────────────────────────────────── + function parseURLs(text) { + return text + .split(/\r?\n/) + .map(l => l.trim()) + .filter(l => { + if (!l) return false; + try { new URL(l); return true; } catch { return false; } + }); + } + + // Extracts every URL from arbitrary text (emails, HTML, markdown, prose…) + function extractURLs(text) { + // Match http/https URLs, including those wrapped in <>, (), [], quotes, or at end of sentence + const raw = text.match(/https?:\/\/[^\s<>"')\]},;]+/gi) || []; + // Deduplicate while preserving order + const seen = new Set(); + return raw + .map(u => { + // Strip common trailing punctuation that isn't part of the URL + return u.replace(/[.,;:!?)"'\]}>]+$/, ''); + }) + .filter(u => { + if (seen.has(u)) return false; + seen.add(u); + try { new URL(u); return true; } catch { return false; } + }); + } + + function setStatus(msg, type = '') { + status.className = 'status' + (type ? ' ' + type : ''); + status.innerHTML = msg + ? `${msg}` + : ''; + } + + function updateCount() { + const urls = parseURLs(urlInput.value); + const n = urls.length; + urlCount.textContent = n === 1 ? '1 URL' : `${n} URLs`; + urlCount.className = 'url-count' + (n > 0 ? ' has-urls' : ''); + openBtn.disabled = n === 0; + } + + function updateBadge() { + if (lazyLoad.checked) { + modeBadge.textContent = 'lazy'; + modeBadge.className = 'mode-badge'; + } else { + modeBadge.textContent = 'eager'; + modeBadge.className = 'mode-badge off'; + } + } + + // ── Event listeners ──────────────────────────────────────── + urlInput.addEventListener('input', () => { + updateCount(); + setStatus(''); + }); + + lazyLoad.addEventListener('change', updateBadge); + + extractBtn.addEventListener('click', () => { + const urls = extractURLs(urlInput.value); + if (!urls.length) { + setStatus('No URLs found in text', 'error'); + return; + } + urlInput.value = urls.join('\n'); + updateCount(); + setStatus(`Extracted ${urls.length} URL${urls.length !== 1 ? 's' : ''}`, 'success'); + + // Brief visual flash on the button + extractBtn.classList.add('flash'); + setTimeout(() => extractBtn.classList.remove('flash'), 600); + }); + + openBtn.addEventListener('click', async () => { + const urls = parseURLs(urlInput.value); + if (!urls.length) return; + + openBtn.disabled = true; + setStatus(''); + + const useLazy = lazyLoad.checked; + let opened = 0; + + for (const url of urls) { + try { + let targetUrl = url; + + if (useLazy) { + // Build the extension loader URL with the target as ?url=… + const loaderBase = chrome.runtime.getURL('loader.html'); + targetUrl = `${loaderBase}?url=${encodeURIComponent(url)}`; + } + + await chrome.tabs.create({ url: targetUrl, active: false }); + opened++; + } catch (err) { + console.warn('Failed to open', url, err); + } + } + + updateCount(); + if (opened === urls.length) { + setStatus(`Opened ${opened} tab${opened !== 1 ? 's' : ''}`, 'success'); + } else { + setStatus(`Opened ${opened} / ${urls.length} tabs`, 'error'); + } + }); + + // ── Init ─────────────────────────────────────────────────── + updateCount(); + updateBadge(); + urlInput.focus(); +})();