Skip to content

Commit 6abe145

Browse files
authored
Add Copy All Links from CTX Menu (#106)
1 parent e105fc3 commit 6abe145

File tree

11 files changed

+215
-93
lines changed

11 files changed

+215
-93
lines changed

manifest.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@
1212
},
1313
"description": "Show Main Popup Action"
1414
},
15-
"extract": {
15+
"extractAll": {
1616
"suggested_key": {
1717
"default": "Alt+Shift+X"
1818
},
1919
"description": "Extract Links from Tab(s)"
20+
},
21+
"extractSelection": {
22+
"description": "Extract Links from Selected Text"
23+
},
24+
"copyAll": {
25+
"suggested_key": {
26+
"default": "Alt+Shift+C"
27+
},
28+
"description": "Copy Links from Tab(s)"
29+
},
30+
"copySelection": {
31+
"description": "Copy Links from Selected Text"
2032
}
2133
},
2234
"omnibox": {

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"pdfjs-dist": "^4.7.76"
2929
},
3030
"devDependencies": {
31-
"@types/chrome": "^0.0.277",
31+
"@types/chrome": "^0.0.278",
3232
"eslint": "^8.57.0",
3333
"gulp": "^4.0.2",
3434
"json-merger": "^1.1.10",

src/html/options.html

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,36 @@ <h1>Link Extractor</h1>
2626
<div class="clearfix"></div>
2727
<p class="text-center lead">v<span class="version"></span></p>
2828

29-
<table id="keyboard-shortcuts" class="table table-sm table-borderless table-hover d-none">
30-
<caption class="visually-hidden">Keyboard Shortcuts</caption>
31-
<thead class="visually-hidden"><tr><th>Description</th><th>Shortcut</th></tr></thead>
32-
<tbody></tbody>
33-
<tfoot class="d-none">
34-
<tr>
35-
<td class="bg-transparent"><i class="fa-regular fa-keyboard me-1"></i> <span class="description"></span></td>
36-
<td class="bg-transparent text-end" title="Keyboard Shortcut"><kbd>Unknown</kbd></td>
37-
</tr>
38-
</tfoot>
39-
</table>
29+
<div id="keyboard-shortcuts" class="d-none">
30+
<div class="d-flex flex-row align-items-center justify-content-center">
31+
<hr class="w-100 my-0">
32+
<span class="text-nowrap mx-2">Keyboard Shortcuts</span>
33+
<hr class="w-100 my-0">
34+
</div>
35+
<table class="table table-sm table-borderless table-hover">
36+
<caption class="visually-hidden">Keyboard Shortcuts</caption>
37+
<thead class="visually-hidden"><tr><th>Description</th><th>Shortcut</th></tr></thead>
38+
<tbody></tbody>
39+
<tfoot class="d-none">
40+
<tr>
41+
<td class="bg-transparent"><i class="fa-regular fa-keyboard me-1"></i> <span class="description"></span></td>
42+
<td class="bg-transparent text-end" title="Keyboard Shortcut"><kbd>Unknown</kbd></td>
43+
</tr>
44+
</tfoot>
45+
</table>
46+
<div class="mb-2">
47+
Manage Keyboard Shortcuts:
48+
<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">
49+
https://mzl.la/3Qwp5QQ <i class="fa-solid fa-arrow-up-right-from-square fa-xs"></i></a>
50+
<a id="chrome-shortcuts" class="d-inline-block d-none chrome" role="button">chrome://extensions/shortcuts</a>
51+
</div>
52+
</div>
53+
54+
<div class="d-flex flex-row align-items-center justify-content-center">
55+
<hr class="w-100 my-0">
56+
<span class="text-nowrap mx-2">General Options</span>
57+
<hr class="w-100 my-0">
58+
</div>
4059

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

127-
<hr>
146+
<div class="d-flex flex-row align-items-center justify-content-center">
147+
<hr class="w-100 my-0">
148+
<span class="text-nowrap mx-2">Saved Filters</span>
149+
<hr class="w-100 my-0">
150+
</div>
128151

129152
<form id="filters-form" class="mb-1">
130153
<label class="form-label" for="add-filter"><i class="fa-solid fa-filter me-2"></i> Filters</label>

src/js/exports.js

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,37 @@ export const githubURL = 'https://github.com/cssnr/link-extractor'
44

55
/**
66
* Inject extract.js to Tab and Open links.html with params
7-
* @function processLinks
7+
* @function injectTab
88
* @param {Object} injectOptions Inject Tab Options
99
* @param {String} [injectOptions.filter] Regex Filter
1010
* @param {Boolean} [injectOptions.domains] Only Domains
1111
* @param {Boolean} [injectOptions.selection] Only Selection
12+
* @param {Boolean} [injectOptions.open] Open Links Page
13+
* @param {chrome.tabs.Tab} [injectOptions.tab] Open Links Page
1214
* @return {Promise<void>}
1315
*/
1416
export async function injectTab({
1517
filter = null,
1618
domains = false,
1719
selection = false,
20+
open = true,
21+
tab = null,
1822
} = {}) {
1923
console.log('injectTab:', filter, domains, selection)
2024

2125
// Extract tabIds from all highlighted tabs
2226
const tabIds = []
23-
const tabs = await chrome.tabs.query({
24-
currentWindow: true,
25-
highlighted: true,
26-
})
27-
if (!tabs.length) {
28-
const [tab] = await chrome.tabs.query({
29-
currentWindow: true,
30-
active: true,
31-
})
32-
console.debug(`tab: ${tab.id}`, tab)
27+
if (tab) {
3328
tabIds.push(tab.id)
3429
} else {
30+
const tabs = await chrome.tabs.query({
31+
currentWindow: true,
32+
highlighted: true,
33+
})
34+
console.debug('tabs:', tabs)
3535
for (const tab of tabs) {
3636
console.debug(`tab: ${tab.id}`, tab)
37-
// tab.url undefined means we do not have permissions on this tab
3837
if (!tab.url) {
39-
// chrome.runtime.openOptionsPage()
4038
const url = new URL(
4139
chrome.runtime.getURL('/html/permissions.html')
4240
)
@@ -49,15 +47,28 @@ export async function injectTab({
4947
tabIds.push(tab.id)
5048
}
5149
}
50+
console.log('tabIds:', tabIds)
5251
if (!tabIds.length) {
53-
console.log('%cNo Tab IDs to Inject', 'color: Yellow')
52+
// TODO: Display Error to User
53+
console.error('No Tab IDs to Inject')
5454
return
5555
}
56-
console.log('tabIds:', tabIds)
5756

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

66+
// Create URL to links.html if open
67+
if (!open) {
68+
console.debug('Skipping opening links.html on !open:', open)
69+
return
70+
}
71+
const url = new URL(chrome.runtime.getURL('/html/links.html'))
6172
// Set URL searchParams
6273
url.searchParams.set('tabs', tabIds.join(','))
6374
if (filter) {
@@ -69,16 +80,6 @@ export async function injectTab({
6980
if (selection) {
7081
url.searchParams.set('selection', selection.toString())
7182
}
72-
73-
// Inject extract.js which listens for messages
74-
for (const tab of tabIds) {
75-
console.debug(`injecting tab.id: ${tab}`)
76-
await chrome.scripting.executeScript({
77-
target: { tabId: tab },
78-
files: ['/js/extract.js'],
79-
})
80-
}
81-
8283
// Open Tab to links.html with desired params
8384
console.debug(`url: ${url.href}`)
8485
await chrome.tabs.create({ active: true, url: url.href })
@@ -394,3 +395,18 @@ export function detectBrowser() {
394395
}
395396
return browser
396397
}
398+
399+
/**
400+
* @function updateBrowser
401+
* @return {Promise<void>}
402+
*/
403+
export function updateBrowser() {
404+
let selector = '.chrome'
405+
// noinspection JSUnresolvedReference
406+
if (typeof browser !== 'undefined') {
407+
selector = '.firefox'
408+
}
409+
document
410+
.querySelectorAll(selector)
411+
.forEach((el) => el.classList.remove('d-none'))
412+
}

src/js/extract.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ function extractSelection() {
9595
if (ancestor.nodeName === '#text') {
9696
continue
9797
}
98-
ancestor.querySelectorAll('a, area').forEach((el) => {
98+
// console.debug('ancestor:', ancestor)
99+
ancestor?.querySelectorAll('a, area')?.forEach((el) => {
99100
if (selection.containsNode(el, true)) {
100101
// console.debug('el:', el)
101102
pushElement(links, el)

src/js/links.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ function handleKeyboard(e) {
544544
input?.focus()
545545
input?.select()
546546
} else if (['KeyT', 'KeyO'].includes(e.code)) {
547+
// noinspection JSIgnoredPromiseFromCall
547548
chrome.runtime.openOptionsPage()
548549
}
549550
}

src/js/options.js

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
onRemoved,
1111
revokePerms,
1212
saveOptions,
13+
updateBrowser,
1314
updateManifest,
1415
updateOptions,
1516
} from './exports.js'
@@ -33,15 +34,20 @@ document
3334
document
3435
.querySelectorAll('.grant-permissions')
3536
.forEach((el) => el.addEventListener('click', grantPerms))
36-
document
37-
.getElementById('options-form')
38-
.addEventListener('submit', (e) => e.preventDefault())
3937
document
4038
.querySelectorAll('#options-form input, select')
4139
.forEach((el) => el.addEventListener('change', saveOptions))
40+
document
41+
.getElementById('options-form')
42+
.addEventListener('submit', (e) => e.preventDefault())
4243
document
4344
.querySelectorAll('[data-bs-toggle="tooltip"]')
4445
.forEach((el) => new bootstrap.Tooltip(el))
46+
document
47+
.getElementById('chrome-shortcuts')
48+
?.addEventListener('click', () =>
49+
chrome.tabs.update({ url: 'chrome://extensions/shortcuts' })
50+
)
4551

4652
document.getElementById('export-data').addEventListener('click', exportClick)
4753
document.getElementById('import-data').addEventListener('click', importClick)
@@ -60,7 +66,15 @@ async function initOptions() {
6066
// noinspection ES6MissingAwait
6167
updateManifest()
6268
// noinspection ES6MissingAwait
63-
setShortcuts()
69+
updateBrowser()
70+
// noinspection ES6MissingAwait
71+
setShortcuts([
72+
'_execute_action',
73+
'extractAll',
74+
'extractSelection',
75+
'copyAll',
76+
'copySelection',
77+
])
6478
// noinspection ES6MissingAwait
6579
checkPerms()
6680
chrome.storage.sync.get(['options', 'patterns']).then((items) => {
@@ -419,24 +433,34 @@ function beginEditing(event, idx) {
419433
/**
420434
* Set Keyboard Shortcuts
421435
* @function setShortcuts
422-
* @param {String} selector
436+
* @param {Array} names
437+
* @param {String} [selector]
438+
* @return {Promise<void>}
423439
*/
424-
async function setShortcuts(selector = '#keyboard-shortcuts') {
440+
async function setShortcuts(names, selector = '#keyboard-shortcuts') {
425441
if (!chrome.commands) {
426442
return console.debug('Skipping: chrome.commands')
427443
}
428-
const table = document.querySelector(selector)
429-
table.classList.remove('d-none')
444+
const parent = document.querySelector(selector)
445+
parent.classList.remove('d-none')
446+
const table = parent.querySelector('table')
447+
console.log('table:', table)
430448
const tbody = table.querySelector('tbody')
431449
const source = table.querySelector('tfoot > tr').cloneNode(true)
450+
// console.log('source:', source)
432451
const commands = await chrome.commands.getAll()
433-
for (const command of commands) {
434-
// console.debug('command:', command)
452+
// console.log('commands:', commands)
453+
for (const name of names) {
454+
const command = commands.find((x) => x.name === name)
455+
console.debug('command:', command)
456+
if (!command) {
457+
console.warn('Command Not Found:', command)
458+
}
435459
const row = source.cloneNode(true)
436-
// TODO: Chrome does not parse the description for _execute_action in manifest.json
437460
let description = command.description
461+
// Note: Chrome does not parse the description for _execute_action in manifest.json
438462
if (!description && command.name === '_execute_action') {
439-
description = 'Show Main Popup Action'
463+
description = 'Show Popup Action'
440464
}
441465
row.querySelector('.description').textContent = description
442466
row.querySelector('kbd').textContent = command.shortcut || 'Not Set'

src/js/popup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ async function popupLinks(event) {
169169
console.debug('href:', href)
170170
let url
171171
if (href.endsWith('html/options.html')) {
172-
chrome.runtime.openOptionsPage()
172+
await chrome.runtime.openOptionsPage()
173173
window.close()
174174
return
175175
} else if (href === '#') {

0 commit comments

Comments
 (0)