127 lines
4.1 KiB
JavaScript
127 lines
4.1 KiB
JavaScript
|
|
(() => {
|
||
|
|
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
|
||
|
|
? `<span class="status-dot"></span><span>${msg}</span>`
|
||
|
|
: '';
|
||
|
|
}
|
||
|
|
|
||
|
|
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();
|
||
|
|
})();
|