Skip to content

Commit

Permalink
Add Copy All Links from CTX Menu (#106)
Browse files Browse the repository at this point in the history
  • Loading branch information
smashedr authored Oct 14, 2024
1 parent e105fc3 commit 6abe145
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 93 deletions.
14 changes: 13 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@
},
"description": "Show Main Popup Action"
},
"extract": {
"extractAll": {
"suggested_key": {
"default": "Alt+Shift+X"
},
"description": "Extract Links from Tab(s)"
},
"extractSelection": {
"description": "Extract Links from Selected Text"
},
"copyAll": {
"suggested_key": {
"default": "Alt+Shift+C"
},
"description": "Copy Links from Tab(s)"
},
"copySelection": {
"description": "Copy Links from Selected Text"
}
},
"omnibox": {
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"pdfjs-dist": "^4.7.76"
},
"devDependencies": {
"@types/chrome": "^0.0.277",
"@types/chrome": "^0.0.278",
"eslint": "^8.57.0",
"gulp": "^4.0.2",
"json-merger": "^1.1.10",
Expand Down
51 changes: 37 additions & 14 deletions src/html/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,36 @@ <h1>Link Extractor</h1>
<div class="clearfix"></div>
<p class="text-center lead">v<span class="version"></span></p>

<table id="keyboard-shortcuts" class="table table-sm table-borderless table-hover d-none">
<caption class="visually-hidden">Keyboard Shortcuts</caption>
<thead class="visually-hidden"><tr><th>Description</th><th>Shortcut</th></tr></thead>
<tbody></tbody>
<tfoot class="d-none">
<tr>
<td class="bg-transparent"><i class="fa-regular fa-keyboard me-1"></i> <span class="description"></span></td>
<td class="bg-transparent text-end" title="Keyboard Shortcut"><kbd>Unknown</kbd></td>
</tr>
</tfoot>
</table>
<div id="keyboard-shortcuts" class="d-none">
<div class="d-flex flex-row align-items-center justify-content-center">
<hr class="w-100 my-0">
<span class="text-nowrap mx-2">Keyboard Shortcuts</span>
<hr class="w-100 my-0">
</div>
<table class="table table-sm table-borderless table-hover">
<caption class="visually-hidden">Keyboard Shortcuts</caption>
<thead class="visually-hidden"><tr><th>Description</th><th>Shortcut</th></tr></thead>
<tbody></tbody>
<tfoot class="d-none">
<tr>
<td class="bg-transparent"><i class="fa-regular fa-keyboard me-1"></i> <span class="description"></span></td>
<td class="bg-transparent text-end" title="Keyboard Shortcut"><kbd>Unknown</kbd></td>
</tr>
</tfoot>
</table>
<div class="mb-2">
Manage Keyboard Shortcuts:
<a class="text-decoration-none d-inline-block d-none firefox" href="https://support.mozilla.org/en-US/kb/manage-extension-shortcuts-firefox" target="_blank" rel="noopener">
https://mzl.la/3Qwp5QQ <i class="fa-solid fa-arrow-up-right-from-square fa-xs"></i></a>
<a id="chrome-shortcuts" class="d-inline-block d-none chrome" role="button">chrome://extensions/shortcuts</a>
</div>
</div>

<div class="d-flex flex-row align-items-center justify-content-center">
<hr class="w-100 my-0">
<span class="text-nowrap mx-2">General Options</span>
<hr class="w-100 my-0">
</div>

<form id="options-form">
<div class="row">
Expand All @@ -48,7 +67,7 @@ <h1>Link Extractor</h1>
Regex Flags for Filtering.
<a class="ms-1 text-decoration-none" target="_blank" rel="noopener"
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#advanced_searching_with_flags">
More Info <i class="fa-solid fa-arrow-up-right-from-square"></i></a>
More Info <i class="fa-solid fa-arrow-up-right-from-square fa-xs"></i></a>
</div>
</div>
</div>
Expand Down Expand Up @@ -115,7 +134,7 @@ <h1>Link Extractor</h1>
data-bs-toggle="tooltip" data-bs-placement="top" data-bs-trigger="hover"
data-bs-title="Allow Extracting Links from Multiple Selected Tabs.">
<i class="fa-solid fa-check-double me-1"></i> Grant Host Permissions</button>
<span class="text-center d-inline-block"><a href="../html/permissions.html">More about Permissions</a></span>
<span class="text-center small d-inline-block"><a href="../html/permissions.html">More about Permissions</a></span>
</div>
<div class="d-none has-perms my-3">
<button class="link-danger btn btn-sm btn-link revoke-permissions" type="button"
Expand All @@ -124,7 +143,11 @@ <h1>Link Extractor</h1>
Remove Host Permissions</button>
</div>

<hr>
<div class="d-flex flex-row align-items-center justify-content-center">
<hr class="w-100 my-0">
<span class="text-nowrap mx-2">Saved Filters</span>
<hr class="w-100 my-0">
</div>

<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
70 changes: 43 additions & 27 deletions src/js/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,37 @@ export const githubURL = 'https://github.com/cssnr/link-extractor'

/**
* Inject extract.js to Tab and Open links.html with params
* @function processLinks
* @function injectTab
* @param {Object} injectOptions Inject Tab Options
* @param {String} [injectOptions.filter] Regex Filter
* @param {Boolean} [injectOptions.domains] Only Domains
* @param {Boolean} [injectOptions.selection] Only Selection
* @param {Boolean} [injectOptions.open] Open Links Page
* @param {chrome.tabs.Tab} [injectOptions.tab] Open Links Page
* @return {Promise<void>}
*/
export async function injectTab({
filter = null,
domains = false,
selection = false,
open = true,
tab = null,
} = {}) {
console.log('injectTab:', filter, domains, selection)

// Extract tabIds from all highlighted tabs
const tabIds = []
const tabs = await chrome.tabs.query({
currentWindow: true,
highlighted: true,
})
if (!tabs.length) {
const [tab] = await chrome.tabs.query({
currentWindow: true,
active: true,
})
console.debug(`tab: ${tab.id}`, tab)
if (tab) {
tabIds.push(tab.id)
} else {
const tabs = await chrome.tabs.query({
currentWindow: true,
highlighted: true,
})
console.debug('tabs:', tabs)
for (const tab of tabs) {
console.debug(`tab: ${tab.id}`, tab)
// tab.url undefined means we do not have permissions on this tab
if (!tab.url) {
// chrome.runtime.openOptionsPage()
const url = new URL(
chrome.runtime.getURL('/html/permissions.html')
)
Expand All @@ -49,15 +47,28 @@ export async function injectTab({
tabIds.push(tab.id)
}
}
console.log('tabIds:', tabIds)
if (!tabIds.length) {
console.log('%cNo Tab IDs to Inject', 'color: Yellow')
// TODO: Display Error to User
console.error('No Tab IDs to Inject')
return
}
console.log('tabIds:', tabIds)

// Create URL to links.html
const url = new URL(chrome.runtime.getURL('/html/links.html'))
// Inject extract.js which listens for messages
for (const tab of tabIds) {
console.debug(`injecting tab.id: ${tab}`)
await chrome.scripting.executeScript({
target: { tabId: tab },
files: ['/js/extract.js'],
})
}

// Create URL to links.html if open
if (!open) {
console.debug('Skipping opening links.html on !open:', open)
return
}
const url = new URL(chrome.runtime.getURL('/html/links.html'))
// Set URL searchParams
url.searchParams.set('tabs', tabIds.join(','))
if (filter) {
Expand All @@ -69,16 +80,6 @@ export async function injectTab({
if (selection) {
url.searchParams.set('selection', selection.toString())
}

// Inject extract.js which listens for messages
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.href}`)
await chrome.tabs.create({ active: true, url: url.href })
Expand Down Expand Up @@ -394,3 +395,18 @@ export function detectBrowser() {
}
return browser
}

/**
* @function updateBrowser
* @return {Promise<void>}
*/
export function updateBrowser() {
let selector = '.chrome'
// noinspection JSUnresolvedReference
if (typeof browser !== 'undefined') {
selector = '.firefox'
}
document
.querySelectorAll(selector)
.forEach((el) => el.classList.remove('d-none'))
}
3 changes: 2 additions & 1 deletion src/js/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ function extractSelection() {
if (ancestor.nodeName === '#text') {
continue
}
ancestor.querySelectorAll('a, area').forEach((el) => {
// console.debug('ancestor:', ancestor)
ancestor?.querySelectorAll('a, area')?.forEach((el) => {
if (selection.containsNode(el, true)) {
// console.debug('el:', el)
pushElement(links, el)
Expand Down
1 change: 1 addition & 0 deletions src/js/links.js
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ function handleKeyboard(e) {
input?.focus()
input?.select()
} else if (['KeyT', 'KeyO'].includes(e.code)) {
// noinspection JSIgnoredPromiseFromCall
chrome.runtime.openOptionsPage()
}
}
48 changes: 36 additions & 12 deletions src/js/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
onRemoved,
revokePerms,
saveOptions,
updateBrowser,
updateManifest,
updateOptions,
} from './exports.js'
Expand All @@ -33,15 +34,20 @@ document
document
.querySelectorAll('.grant-permissions')
.forEach((el) => el.addEventListener('click', grantPerms))
document
.getElementById('options-form')
.addEventListener('submit', (e) => e.preventDefault())
document
.querySelectorAll('#options-form input, select')
.forEach((el) => el.addEventListener('change', saveOptions))
document
.getElementById('options-form')
.addEventListener('submit', (e) => e.preventDefault())
document
.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach((el) => new bootstrap.Tooltip(el))
document
.getElementById('chrome-shortcuts')
?.addEventListener('click', () =>
chrome.tabs.update({ url: 'chrome://extensions/shortcuts' })
)

document.getElementById('export-data').addEventListener('click', exportClick)
document.getElementById('import-data').addEventListener('click', importClick)
Expand All @@ -60,7 +66,15 @@ async function initOptions() {
// noinspection ES6MissingAwait
updateManifest()
// noinspection ES6MissingAwait
setShortcuts()
updateBrowser()
// noinspection ES6MissingAwait
setShortcuts([
'_execute_action',
'extractAll',
'extractSelection',
'copyAll',
'copySelection',
])
// noinspection ES6MissingAwait
checkPerms()
chrome.storage.sync.get(['options', 'patterns']).then((items) => {
Expand Down Expand Up @@ -419,24 +433,34 @@ function beginEditing(event, idx) {
/**
* Set Keyboard Shortcuts
* @function setShortcuts
* @param {String} selector
* @param {Array} names
* @param {String} [selector]
* @return {Promise<void>}
*/
async function setShortcuts(selector = '#keyboard-shortcuts') {
async function setShortcuts(names, selector = '#keyboard-shortcuts') {
if (!chrome.commands) {
return console.debug('Skipping: chrome.commands')
}
const table = document.querySelector(selector)
table.classList.remove('d-none')
const parent = document.querySelector(selector)
parent.classList.remove('d-none')
const table = parent.querySelector('table')
console.log('table:', table)
const tbody = table.querySelector('tbody')
const source = table.querySelector('tfoot > tr').cloneNode(true)
// console.log('source:', source)
const commands = await chrome.commands.getAll()
for (const command of commands) {
// console.debug('command:', command)
// console.log('commands:', commands)
for (const name of names) {
const command = commands.find((x) => x.name === name)
console.debug('command:', command)
if (!command) {
console.warn('Command Not Found:', command)
}
const row = source.cloneNode(true)
// TODO: Chrome does not parse the description for _execute_action in manifest.json
let description = command.description
// Note: Chrome does not parse the description for _execute_action in manifest.json
if (!description && command.name === '_execute_action') {
description = 'Show Main Popup Action'
description = 'Show Popup Action'
}
row.querySelector('.description').textContent = description
row.querySelector('kbd').textContent = command.shortcut || 'Not Set'
Expand Down
2 changes: 1 addition & 1 deletion src/js/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ async function popupLinks(event) {
console.debug('href:', href)
let url
if (href.endsWith('html/options.html')) {
chrome.runtime.openOptionsPage()
await chrome.runtime.openOptionsPage()
window.close()
return
} else if (href === '#') {
Expand Down
Loading

0 comments on commit 6abe145

Please sign in to comment.