Skip to content

Commit

Permalink
Add Open Links Feature (#14)
Browse files Browse the repository at this point in the history
* Add Open Links Feature

* Tweak Open Links and Cleanup

* Add Parse Links and Cleanup

* Make Extra Text on Links Not Selectable

* Update README.md
  • Loading branch information
smashedr authored Oct 28, 2023
1 parent 977e92e commit 162e145
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 55 deletions.
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[![Build](https://github.com/cssnr/link-extractor/actions/workflows/build.yaml/badge.svg)](https://github.com/cssnr/link-extractor/actions/workflows/build.yaml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cssnr_link-extractor&metric=alert_status)](https://sonarcloud.io/summary/overall?id=cssnr_link-extractor)
[![GitHub Release](https://img.shields.io/github/v/release/cssnr/link-extractor)](https://github.com/cssnr/link-extractor/releases/latest)
[![Chrome Web Store Version](https://img.shields.io/chrome-web-store/v/ifefifghpkllfibejafbakmflidjcjfp?label=chrome&logo=googlechrome)](https://chrome.google.com/webstore/detail/link-extractor/ifefifghpkllfibejafbakmflidjcjfp)
[![Mozilla Add-on Version](https://img.shields.io/amo/v/link-extractor?label=firefox&logo=firefox)](https://addons.mozilla.org/addon/link-extractor)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cssnr_link-extractor&metric=alert_status)](https://sonarcloud.io/summary/overall?id=cssnr_link-extractor)
[![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/ifefifghpkllfibejafbakmflidjcjfp?logo=google&logoColor=white&label=google%20users)](https://chrome.google.com/webstore/detail/link-extractor/ifefifghpkllfibejafbakmflidjcjfp)
[![Mozilla Add-on Users](https://img.shields.io/amo/users/link-extractor?logo=mozilla&label=mozilla%20users)](https://addons.mozilla.org/addon/link-extractor)
# Link Extractor

Modern Chrome and Firefox Addon to easily extract all links/domains from a web page with optional filters.
Including automatic dark/light mode, copy all links or domains, striped tables, and more...
Modern Chrome and Firefox Addon to easily extract, parse, and open all links/domains from a web page with optional filters.
Feature packed with automatic dark/light mode, copy to clipboard, keyboard shortcuts, custom options, and much more...

* [Install](#install)
* [Features](#features)
Expand All @@ -28,12 +28,14 @@ Please submit a [Feature Request](https://github.com/cssnr/link-extractor/discus
for any new features you would like to see implemented.

* Extract all Links and/or Domains
* Parse or open links from text or clipboard
* Copy all URLs or Domains to the clipboard
* Quick Filter URLs with a regular expression
* Quick Filter links by a saved regular expressions
* Automatic Dark/Light mode based on browser setting
* Keyboard Shortcuts for Copying Links or Domains

[![Screenshot of Links and Popup](https://repository-images.githubusercontent.com/707614074/fd2e2fe9-d896-42eb-80be-603f5230d36e)](https://github.com/cssnr/link-extractor)
[![Screenshot of Links and Popup](https://repository-images.githubusercontent.com/707614074/7807bbb5-ec14-4fae-85d8-e3252a460cff)](https://github.com/cssnr/link-extractor)

# Configuration

Expand All @@ -50,6 +52,9 @@ Make sure to click`Save Options` when finished. For more information on regex, s
To build locally or run from source, clone the repository then run `npm install`.
You can then run the addon from the [src](src) directory as normal.

NPM is only used to manage dependency versions and copy files to `src/dist`.
Files are copied automatically after `npm install`. See [gulpfile.js](gulpfile.js) for more information.

The extension is automatically built on new releases then automatically uploaded to that release.
See [build.yaml](.github/workflows/build.yaml) for more information.

Expand All @@ -63,12 +68,17 @@ See [build.yaml](.github/workflows/build.yaml) for more information.

## Firefox

For development, you can and should load unpacked in Firefox as a temporary addon.
This will **not** remain after restarting Firefox. It is also useful to keep data after removing an extension.

1. Download a [Release](https://github.com/cssnr/link-extractor/releases).
1. Load temporary from: `about:debugging#/runtime/this-firefox`
1. Open `about:config` search for `extensions.webextensions.keepStorageOnUninstall` and set to `true`.

> **Note**
>
> This **does not** work on Release Firefox!
> This method **does not** work on Release Firefox and is NOT recommended for development.
> You must use [ESR](https://www.mozilla.org/en-CA/firefox/all/#product-desktop-esr), Development, or Nightly.
1. Download a [Release](https://github.com/cssnr/link-extractor/releases).
1. Open `about:config` search for `xpinstall.signatures.required` and set to `false`.
1. Open `about:addons` and drag the zip file to the page or choose Install from File from the Settings wheel.
1. You may also load temporary from: `about:debugging#/runtime/this-firefox`
5 changes: 3 additions & 2 deletions src/css/popup.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
body {
width: 280px;
width: 300px;
}

input::placeholder {
input::placeholder,
textarea::placeholder {
text-align: center;
}
16 changes: 10 additions & 6 deletions src/html/links.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@
<h2 id="message">Loading...</h2>

<div class="links" style="display: none">
<h2>Links</h2>
<a id="links-clip" class="clip btn btn-sm btn-outline-success mb-2" role="button" data-clipboard-text="">
Copy Links</a> Press <kbd>C</kbd> or <kbd>L</kbd> to Copy Links.
<div class="user-select-none">
<h2>Links</h2>
<a id="links-clip" class="clip btn btn-sm btn-outline-success mb-2" role="button" data-clipboard-text="">
Copy Links</a> Press <kbd>C</kbd> or <kbd>L</kbd> to Copy Links.
</div>
<table id="links" class="table table-sm table-striped table-hover">
<caption class="visually-hidden">Links</caption>
<tbody></tbody>
</table>
</div>

<div class="domains" style="display: none">
<h2>Domains</h2>
<a id="domains-clip" class="clip btn btn-sm btn-outline-primary mb-2" role="button" data-clipboard-text="">
Copy Domains</a> Press <kbd>D</kbd> or <kbd>M</kbd> to Copy Domains.
<div class="user-select-none">
<h2>Domains</h2>
<a id="domains-clip" class="clip btn btn-sm btn-outline-primary mb-2" role="button" data-clipboard-text="">
Copy Domains</a> Press <kbd>D</kbd> or <kbd>M</kbd> to Copy Domains.
</div>
<table id="domains" class="table table-sm table-striped table-hover">
<caption class="visually-hidden">Domains</caption>
<tbody></tbody>
Expand Down
25 changes: 20 additions & 5 deletions src/html/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<html lang="en">
<head>
<title>Link Extractor</title>
<link rel="icon" href="../images/logo16.png" sizes="any">
<link rel="stylesheet" type="text/css" href="../dist/bootstrap/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="../css/popup.css">
<script type="text/javascript" src="../js/theme.js"></script>
Expand All @@ -11,34 +12,48 @@
<div class="container-fluid p-3">
<div class="d-grid g-2 gap-3">
<div class="btn-group btn-group-sm" role="group" aria-label="Button group with nested dropdown">
<button id="btn-all" class="btn btn-success btn-sm popup-click" type="button">All Links</button>
<button id="btn-all" class="btn btn-success btn-sm popup-click" type="button">
All Links</button>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-success btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> Filters</button>
<button type="button" class="btn btn-outline-success btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
Filters</button>
<ul id="filters-ul" class="dropdown-menu">
<li id="no-filters"><a class="dropdown-item popup-click" href="#" data-href="html/options.html">No Saved Filters</a></li>
</ul>
</div>
</div>
<form id="filter-form" class="my-0">
<label for="filter-input" class="visually-hidden"></label>
<input id="filter-input" class="form-control" type="text" placeholder="Filter">
<input id="filter-input" class="form-control form-control-sm" type="text" placeholder="Quick Filter">
<!-- <div class="input-group mb-3">-->
<!-- <input type="text" id="filter-input" class="form-control w-75" placeholder="Filter" aria-label="Filter">-->
<!-- <input type="text" id="flags-input" class="form-control w-25" placeholder="Flags" aria-label="Flags">-->
<!-- </div>-->
</form>
<button id="btn-domains" class="btn btn-primary btn-sm popup-click" type="button">Only Domains</button>
<hr class="my-0">
<form id="links-form" class="my-0">
<label for="links-text" class="form-label visually-hidden">Open Links</label>
<textarea id="links-text" class="form-control form-control-sm" rows="2" placeholder="Paste Links to Parse/Open"></textarea>
</form>
<div class="btn-group btn-group-sm w-100" role="group" aria-label="Open Links">
<button type="submit" form="links-form" class="btn btn-outline-warning disabled" id="parse-links" data-bs-toggle="tooltip"
data-bs-placement="bottom" data-bs-title="Parse All Links" data-text="Parse">Parse</button>
<button type="submit" form="links-form" class="btn btn-outline-warning disabled" id="open-links" data-bs-toggle="tooltip"
data-bs-placement="bottom" data-bs-title="Open All Links" data-text="Open">Open</button>
</div>
<button class="btn btn-outline-info btn-sm popup-click" type="button" data-href="html/options.html">Options</button>
<p class="mb-0 text-center"><small>
<p class="mb-0 text-center small">
<a id="btn-about" class="link-offset-2 link-underline link-underline-opacity-0 link-underline-opacity-75-hover popup-click" type="button" rel="noopener"
data-href="">Link Extractor</a> <span id="version" class=""></span>
</small></p>
</p>
</div>
</div>

<script type="text/javascript" src="../dist/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../dist/bootstrap/bootstrap.bundle.min.js"></script>
<script type="text/javascript" src="../dist/clipboard/clipboard.min.js"></script>
<script type="text/javascript" src="../js/main.js"></script>
<script type="text/javascript" src="../js/popup.js"></script>

</body>
Expand Down
3 changes: 2 additions & 1 deletion src/js/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ chrome.runtime.onMessage.addListener(onMessage)
* @param {function} sendResponse
*/
function onMessage(message, sender, sendResponse) {
console.log(`onMessage: ${message.action}`)
console.log(`onMessage: message.action: ${message.action}`)
if (message.action === 'extract') {
sendResponse(extractLinks())
}
Expand All @@ -19,6 +19,7 @@ function onMessage(message, sender, sendResponse) {
/**
* Extract links
* @function extractLinks
* @return array
*/
function extractLinks() {
console.log('extractLinks')
Expand Down
56 changes: 38 additions & 18 deletions src/js/links.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
// JS for links.html

window.addEventListener('keydown', checkKey)
document.addEventListener('DOMContentLoaded', initLinks)

const urlParams = new URLSearchParams(window.location.search)
const tabId = parseInt(urlParams.get('tab'))

chrome.tabs.sendMessage(tabId, { action: 'extract' }, (links) => {
processLinks(links)
})
/**
* Links Init
* TODO: Review this function
* @function initLinks
*/
async function initLinks() {
if (urlParams.has('popup')) {
const links = await chrome.runtime.sendMessage({
msg: 'extract',
})
await processLinks(links)
} else if (tabId) {
chrome.tabs.sendMessage(tabId, { action: 'extract' }, (links) => {
processLinks(links)
})
} else {
console.error('No Data to Process...')
alert('No Data to Process...')
window.close()
}
}

/**
* Process Links
* TODO: Cleanup this function
* @function processLinks
* @param links
* @param {array} links
*/
async function processLinks(links) {
// TODO: Cleanup this WHOLE function...
console.log('processLinks:', links)
const urlFilter = urlParams.get('filter')
const onlyDomains = urlParams.has('domains')
console.log(`urlFilter: ${urlFilter}`)
console.log(`onlyDomains: ${onlyDomains}`)
console.log(links)

if (chrome.runtime.lastError) {
alert(chrome.runtime.lastError)
Expand Down Expand Up @@ -80,8 +99,8 @@ async function processLinks(links) {
/**
* Update Table with URLs
* @function addNodes
* @param {Array} data
* @param {String} elementId
* @param {array} data
* @param {string} elementId
*/
function updateTable(data, elementId) {
const tbody = document
Expand All @@ -100,6 +119,7 @@ function updateTable(data, elementId) {
* Get base URL of link
* @function getBaseURL
* @param {string} link
* @return string
*/
function getBaseURL(link) {
const reBaseURL = /(^\w+:\/\/[^/]+)|(^[A-Za-z0-9.-]+)\/|(^[A-Za-z0-9.-]+$)/
Expand All @@ -116,21 +136,21 @@ function getBaseURL(link) {
/**
* Keyboard Callback
* @function checkKey
* @param {onkeydown} event
* @param {KeyboardEvent} event
*/
function checkKey(event) {
const formElements = ['INPUT', 'TEXTAREA', 'SELECT', 'OPTION']
if (!formElements.includes(event.target.tagName)) {
console.log(event.keyCode)
if (event.keyCode === 67 || event.keyCode === 76) {
document.getElementById('links-clip').click() // C|L
} else if (event.keyCode === 68 || event.keyCode === 77) {
document.getElementById('domains-clip').click() // D|M
} else if (event.keyCode === 84 || event.keyCode === 79) {
// console.log(event.code)
if (event.code === 'KeyC' || event.code === 'KeyL') {
document.getElementById('links-clip').click()
} else if (event.code === 'KeyD' || event.code === 'KeyM') {
document.getElementById('domains-clip').click()
} else if (event.code === 'KeyT' || event.code === 'KeyO') {
const url = chrome.runtime.getURL('../html/options.html')
chrome.tabs.create({ active: true, url: url }).then() // T|O
} else if (event.keyCode === 90 || event.keyCode === 75) {
$('#keybinds-modal').modal('toggle') // Z|K
chrome.tabs.create({ active: true, url: url }).then()
} else if (event.code === 'KeyZ' || event.code === 'KeyK') {
$('#keybinds-modal').modal('toggle')
}
}
}
24 changes: 18 additions & 6 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const clipboard = new ClipboardJS('.clip')

clipboard.on('success', function (event) {
console.info('event:', event)
console.info('clipboard.success:', event)
if (event.trigger.id === 'links-clip') {
showToast('Links Copied')
} else if (event.trigger.id === 'domains-clip') {
Expand All @@ -14,19 +14,18 @@ clipboard.on('success', function (event) {
})

clipboard.on('error', function (event) {
console.error('event:', event)
console.log('clipboard.error:', event)
showToast('Clipboard Copy Failed', 'warning')
})

/**
* Show Bootstrap Toast
* Requires: jQuery
* TODO: Remove jQuery Dependency
* @function showToast
* @param {String} message
* @param {String} bsClass
* @param {string} message
* @param {string} bsClass
*/
function showToast(message, bsClass = 'success') {
// TODO: Remove jQuery Dependency
const toastEl = $(
'<div class="toast align-items-center border-0 mt-3" role="alert" aria-live="assertive" aria-atomic="true">\n' +
' <div class="d-flex">\n' +
Expand All @@ -41,3 +40,16 @@ function showToast(message, bsClass = 'success') {
const toast = new bootstrap.Toast(toastEl)
toast.show()
}

/**
* Open Links in Tabs
* @function openLinks
* @param {array} links
* @param {boolean} active
*/
function openLinksInTabs(links, active = true) {
console.log('openLinksInTabs:', links)
links.forEach(function (url) {
chrome.tabs.create({ active, url }).then()
})
}
12 changes: 6 additions & 6 deletions src/js/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async function initOptions() {
*/
function deleteInputFilter(event) {
event.preventDefault()
console.log(event)
console.log('deleteInputFilter:', event)
const inputs = document
.getElementById('filters-inputs')
.getElementsByTagName('input').length
Expand All @@ -63,7 +63,7 @@ function deleteInputFilter(event) {
*/
function addInputFilter(event) {
event.preventDefault()
console.log(event)
console.log('addInputFilter:', event)
const el = document.getElementById('filters-inputs')
const next = (parseInt(el.lastChild.dataset.id) + 1).toString()
createFilterInput(next)
Expand All @@ -72,8 +72,8 @@ function addInputFilter(event) {
/**
* Add Form Input for a Filter
* @function createFilterInput
* @param {String} number
* @param {String} value
* @param {string} number
* @param {string} value
*/
function createFilterInput(number, value = '') {
const el = document.getElementById('filters-inputs')
Expand All @@ -98,7 +98,7 @@ function createFilterInput(number, value = '') {
*/
async function saveOptions(event) {
event.preventDefault()
console.log('saveOptions')
console.log('saveOptions:', event)
const options = {}
const input = document.getElementById('reFlags')
let flags = input.value.toLowerCase().replace(/\s+/gm, '').split('')
Expand Down Expand Up @@ -133,7 +133,7 @@ async function saveOptions(event) {
*/
function resetForm(event) {
event.preventDefault()
console.log(event)
console.log('resetForm:', event)
const input = document.getElementById('reFlags')
input.value = 'ig'
input.focus()
Expand Down
Loading

0 comments on commit 162e145

Please sign in to comment.