Skip to content

Commit

Permalink
Extract Links from Multiple Tabs (#66)
Browse files Browse the repository at this point in the history
* Extract Links from Multiple Tabs

* Always work on highlighted tabs

* Add Optional Host Permissions

* Update Permissions Handling

* Update Permissions Functions

* Update injectTab for use from all methods

* Update Logging

* Update README.md
  • Loading branch information
smashedr authored May 8, 2024
1 parent b4c7466 commit b08243c
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 30 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ For any issues, bugs or concerns; please [Open an Issue](https://github.com/cssn
* Extract All Links and Domains from Any Site
* Extract Links from Selected Text on any Site
* Extract Links from Clipboard or Any Text
* Extract Links from All Selected Tabs
* Open Multiple Links in Tabs from Text
* Download Links and Domains as a Text File
* Copy the Text from a Link via Context Menu
Expand Down
1 change: 1 addition & 0 deletions manifest-chrome.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"optional_host_permissions": ["*://*/*"],
"background": {
"service_worker": "js/service-worker.js"
},
Expand Down
1 change: 1 addition & 0 deletions manifest-firefox.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"optional_permissions": ["*://*/*"],
"background": {
"scripts": ["js/service-worker.js"]
},
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Easily extract, parse, or open all links/domains from a site or text with optional filters.",
"homepage_url": "https://link-extractor.cssnr.com/",
"author": "Shane",
"version": "0.4.4",
"version": "0.5.0",
"manifest_version": 3,
"commands": {
"_execute_action": {
Expand Down
11 changes: 11 additions & 0 deletions src/html/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ <h1>Link Extractor</h1>
</div>
</form>

<div class="d-none grant-perms my-3">
<a id="grant-perms" class="btn btn-sm btn-outline-success mb-2" role="button"
data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Allow Extracting Links from Multiple Selected Tabs.">
<i class="fa-solid fa-check-double me-1"></i> Grant Host Permissions</a>
</div>
<div class="d-none has-perms my-3">
<a id="revoke-perms" class="link-danger" role="button"
data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="No Longer Allow Extracting Links from Multiple Tabs.">
Remove Host Permissions</a>
</div>

<hr>
<form id="filters-form" class="mb-1">
<label class="form-label" for="add-filter"><i class="fa-solid fa-filter me-2"></i> Filters</label>
Expand Down
7 changes: 7 additions & 0 deletions src/html/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<div class="container-fluid p-3">
<div class="d-grid g-2 gap-2">

<div class="btn-group btn-group-sm" role="group" aria-label="Button group with nested dropdown">
<a role="button" class="btn btn-sm btn-success" data-filter="">
<i class="fa-solid fa-link me-1"></i> All Links</a>
Expand Down Expand Up @@ -66,6 +67,12 @@
</div>
</form>

<div class="d-none grant-perms">
<a id="grant-perms" class="btn btn-sm btn-outline-success w-100" role="button"
data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Allow Extracting Links from Multiple Selected Tabs.">
<i class="fa-solid fa-check-double me-1"></i> Grant Host Permissions</a>
</div>

<a class="btn btn-sm btn-outline-info" role="button" href="../html/options.html">
<i class="fa-solid fa-sliders me-1"></i> Options</a>
<div class="d-flex justify-content-center align-items-center text-center small">
Expand Down
127 changes: 110 additions & 17 deletions src/js/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,41 @@
/**
* Inject extract.js to Tab and Open links.html with params
* @function processLinks
* @param {String} filter Regex Filter
* @param {Boolean} domains Only Domains
* @param {Boolean} selection Only Selection
* @param {String} [filter] Regex Filter
* @param {Boolean} [domains] Only Domains
* @param {Boolean} [selection] Only Selection
* @param {chrome.tabs[]} tabs Tabs for Extraction
*/
export async function injectTab({
filter = null,
domains = false,
selection = false,
} = {}) {
console.log('injectTab:', filter, domains, selection)
const tabIds = []

// Get Current Tab
const [tab] = await chrome.tabs.query({ currentWindow: true, active: true })
console.debug(`tab: ${tab.id}`, tab)
// Extract tabIds from tabs
const tabs = await chrome.tabs.query({ highlighted: true })
if (!tabs.length) {
const [tab] = await chrome.tabs.query({
currentWindow: true,
active: true,
})
console.debug(`tab: ${tab.id}`, tab)
tabIds.push(tab.id)
} else {
for (const tab of tabs) {
console.debug(`tab: ${tab.id}`, tab)
tabIds.push(tab.id)
}
}
console.log('tabIds:', tabIds)

// Create URL to links.html
const url = new URL(chrome.runtime.getURL('../html/links.html'))

// Set URL searchParams
url.searchParams.set('tab', tab.id.toString())
url.searchParams.set('tabs', tabIds.join(','))
if (filter) {
url.searchParams.set('filter', filter)
}
Expand All @@ -34,11 +49,13 @@ export async function injectTab({
}

// Inject extract.js which listens for messages
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['/js/extract.js'],
})

for (const tab of tabIds) {
console.debug(`injecting tab.id: ${tab}`)
await chrome.scripting.executeScript({
target: { tabId: tab },
files: ['/js/extract.js'],
})
}
// Open Tab to links.html with desired params
console.debug(`url: ${url.toString()}`)
await chrome.tabs.create({ active: true, url: url.toString() })
Expand Down Expand Up @@ -113,10 +130,9 @@ export async function exportClick(event) {
event.preventDefault()
const name = event.target.dataset.importName
console.debug('name:', name)
const display = event.target.dataset.importDisplay
console.debug('display:', display)
const display = event.target.dataset.importDisplay || name
const data = await chrome.storage.sync.get()
console.debug('data:', data[name])
// console.debug('data:', data[name])
if (!data[name].length) {
return showToast(`No ${display} Found!`, 'warning')
}
Expand Down Expand Up @@ -146,8 +162,8 @@ export async function importChange(event) {
event.preventDefault()
const name = event.target.dataset.importName
console.debug('name:', name)
const display = event.target.dataset.importDisplay
console.debug('display:', display)
const display = event.target.dataset.importDisplay || name
// console.debug('display:', display)
const importInput = document.getElementById('import-input')
if (!importInput.files?.length) {
return console.debug('No importInput.files', importInput)
Expand Down Expand Up @@ -198,3 +214,80 @@ export function textFileDownload(filename, text) {
element.click()
document.body.removeChild(element)
}

/**
* Request Host Permissions
* @function requestPerms
* @return {chrome.permissions.request}
*/
export async function requestPerms() {
return await chrome.permissions.request({
origins: ['*://*/*'],
})
}

/**
* Check Host Permissions
* @function checkPerms
* @return {Boolean}
*/
export async function checkPerms() {
const hasPerms = await chrome.permissions.contains({
origins: ['*://*/*'],
})
console.debug('checkPerms:', hasPerms)
// Firefox still uses DOM Based Background Scripts
if (typeof document === 'undefined') {
return hasPerms
}
const hasPermsEl = document.querySelectorAll('.has-perms')
const grantPermsEl = document.querySelectorAll('.grant-perms')
if (hasPerms) {
hasPermsEl.forEach((el) => el.classList.remove('d-none'))
grantPermsEl.forEach((el) => el.classList.add('d-none'))
} else {
grantPermsEl.forEach((el) => el.classList.remove('d-none'))
hasPermsEl.forEach((el) => el.classList.add('d-none'))
}
return hasPerms
}

/**
* Revoke Permissions Click Callback
* NOTE: For many reasons Chrome will determine host_perms are required and
* will ask for them at install time and not allow them to be revoked
* @function revokePerms
* @param {Event} event
*/
export async function revokePerms(event) {
console.debug('revokePerms:', event)
const permissions = await chrome.permissions.getAll()
console.debug('permissions:', permissions)
try {
await chrome.permissions.remove({
origins: permissions.origins,
})
await checkPerms()
} catch (e) {
console.log(e)
showToast(e.toString(), 'danger')
}
}

/**
* Permissions On Added Callback
* @param permissions
*/
export async function onAdded(permissions) {
console.debug('onAdded', permissions)
await checkPerms()
}

/**
* Permissions On Added Callback
* @param permissions
*/
export async function onRemoved(permissions) {
console.debug('onRemoved', permissions)
await checkPerms()
}
30 changes: 23 additions & 7 deletions src/js/links.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,34 @@ const dtOptions = {
async function initLinks() {
console.log('initLinks: urlParams:', urlParams)
try {
const tabId = parseInt(urlParams.get('tab'))
const tabIds = urlParams.get('tabs')
const tabs = tabIds.split(',')
console.log('tabs:', tabs)
const selection = urlParams.has('selection')
console.debug(`tabId: ${tabId}, selection: ${selection}`)
// console.debug(`tabId: ${tabId}, selection: ${selection}`)

if (tabId) {
const action = selection ? 'selection' : 'all'
const links = await chrome.tabs.sendMessage(tabId, action)
await processLinks(links)
// TODO: Populate Links to links then processLinks
const allLinks = []
if (tabs.length) {
console.log('processing tabs:', tabs)
// const tabId = parseInt(tabs[0])
for (const tabId of tabs) {
console.log('tabId:', tabId)
const action = selection ? 'selection' : 'all'
console.log('action:', action)
const links = await chrome.tabs.sendMessage(
parseInt(tabId),
action
)
allLinks.push(...links)
// await processLinks(links)
}
} else {
const { links } = await chrome.storage.local.get(['links'])
await processLinks(links)
allLinks.push(...links)
// await processLinks(links)
}
await processLinks(allLinks)
} catch (e) {
console.warn('error:', e)
alert('Error Processing Results. See Console for More Details...')
Expand Down
2 changes: 1 addition & 1 deletion src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function showToast(message, type = 'success') {
* @param {Function} fn
* @param {Number} timeout
*/
function debounce(fn, timeout = 300) {
function debounce(fn, timeout = 250) {
let timeoutID
return (...args) => {
clearTimeout(timeoutID)
Expand Down
27 changes: 24 additions & 3 deletions src/js/options.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
// JS for options.html

import {
checkPerms,
exportClick,
importChange,
importClick,
onAdded,
onRemoved,
requestPerms,
revokePerms,
saveOptions,
updateOptions,
} from './exports.js'

chrome.storage.onChanged.addListener(onChanged)
chrome.permissions.onAdded.addListener(onAdded)
chrome.permissions.onRemoved.addListener(onRemoved)

document.addEventListener('DOMContentLoaded', initOptions)
document.addEventListener('blur', filterClick)
document.addEventListener('click', filterClick)
document.getElementById('update-filter').addEventListener('submit', filterClick)
document.getElementById('filters-form').addEventListener('submit', addFilter)
document.getElementById('reset-default').addEventListener('click', resetForm)
document
.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach((el) => new bootstrap.Tooltip(el))
document.getElementById('grant-perms').addEventListener('click', grantPerms)
document.getElementById('revoke-perms').addEventListener('click', revokePerms)

const optionsForm = document.getElementById('options-form')
optionsForm.addEventListener('submit', (e) => e.preventDefault())
optionsForm
.querySelectorAll('input, select')
.forEach((el) => el.addEventListener('change', saveOptions))
document
.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach((el) => new bootstrap.Tooltip(el))

// Data Import/Export
document.getElementById('export-data').addEventListener('click', exportClick)
Expand Down Expand Up @@ -53,6 +63,7 @@ async function initOptions() {
document.getElementById('extractKey').textContent =
commands.find((x) => x.name === 'extract').shortcut || 'Not Set'

await checkPerms()
document.getElementById('add-filter').focus()
}

Expand Down Expand Up @@ -306,3 +317,13 @@ async function resetForm(event) {
input.focus()
await saveOptions(event)
}

/**
* Grant Permissions Click Callback
* @function grantPerms
* @param {MouseEvent} event
*/
export async function grantPerms(event) {
console.debug('grantPerms:', event)
await requestPerms()
}
Loading

0 comments on commit b08243c

Please sign in to comment.