mirror of
https://github.com/SolninjaA/Chhoto-URL-Extension.git
synced 2025-02-27 10:19:33 +10:00
v1.2.0 - Add manual URL generation
This commit is contained in:
parent
888138e93b
commit
ebe07d8f8b
10
README.md
10
README.md
@ -46,7 +46,7 @@ Once you have installed the extension, either through an extension store or [fro
|
||||
### Firefox
|
||||
1. Right-click on the Chhoto URL extension icon (). If the extension is not pinned, the Chhoto URL extension icon will be under this highlighted icon:
|
||||
|
||||

|
||||

|
||||
|
||||
2. Select "Manage Extension"
|
||||
3. Select "Preferences"
|
||||
@ -55,7 +55,7 @@ Once you have installed the extension, either through an extension store or [fro
|
||||
### Chromium-based browsers
|
||||
1. **(if the extension is not pinned)** Click on the icon that is most similar to the highlighted icon in this image:
|
||||
|
||||

|
||||

|
||||
|
||||
2. **(if the extension is not pinned)** Click on the pin icon next to the Chhoto URL extension icon
|
||||
3. Right-click on the Chhoto URL extension icon ()
|
||||
@ -70,15 +70,16 @@ This extension only communicates to the Chhoto URL server instance you configure
|
||||
|
||||
### Extension Permissions
|
||||
| Permission | Required so that the extension can... |
|
||||
| --------------------------------- | ---------------------------------------------------------------------|
|
||||
|-----------------------------------|---------------------------------------------------------------------------------|
|
||||
| [`activeTab`][tabs-api] | Get the active tab's URL |
|
||||
| [`notifications`][notif-api] | Inform users if generating the shortened link was successful or not. |
|
||||
| [`clipboardWrite`][clipboard-api] | Copy the shortened link to your clipboard. |
|
||||
| [`storage`][storage-api] | Save the extension settings in your local browser storage. |
|
||||
| [`https://*/*`][host-permission] | Contact the configured Chhoto URL server instance. |
|
||||
| [`contextMenus`][context-menus] | Add an item (which opens a popup for manual URL generation) to the context menu |
|
||||
|
||||
## Installing from source
|
||||
1. Run `git clone https://git.solomon.tech/solomon/Chhoto-URL-Extension.git`
|
||||
1. Run `git clone https://github.com/SolninjaA/Chhoto-URL-Extension.git`
|
||||
2. Install the extension (see below for how to install the extension on common browsers)
|
||||
|
||||
### Installing from source on Firefox
|
||||
@ -115,4 +116,5 @@ This project was inspired by and modified from Edward Shen's [Shlink extension][
|
||||
[clipboard-api]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/clipboard
|
||||
[storage-api]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage
|
||||
[host-permission]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#host_permissions
|
||||
[context-menus]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus
|
||||
[shlink-extension]: https://github.com/edward-shen/shlink
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
/**
|
||||
* File Overview
|
||||
* @file This file actually creates a shortened link. "options.js" is only for the options page - this file handles the main extension functionality
|
||||
* @file This file actually creates a shortened link. "options.js" is only for the options page, "popup.js" calles this script to generate a shortened link - this file handles the main extension functionality
|
||||
* @copyright Solomon Tech 2025
|
||||
*/
|
||||
|
||||
@ -74,9 +74,10 @@
|
||||
*
|
||||
* @param {!URL} url
|
||||
* @param {!string} title
|
||||
* @returns {!Promise<[URL, string], Error>}
|
||||
* @param {!string} type
|
||||
* @returns {!Promise<[URL, string, string], Error>}
|
||||
*/
|
||||
function validateURL(url, title) {
|
||||
function validateURL(url, title, type) {
|
||||
return browser.storage.local.get("allowedProtocols").then(({ allowedProtocols }) => {
|
||||
// Initialize a list of protocols that are allowed if unset.
|
||||
// This needs to be synced with the initialization code in options.js.
|
||||
@ -97,21 +98,30 @@ function validateURL(url, title) {
|
||||
}
|
||||
|
||||
// Return URL and title
|
||||
return Promise.resolve([url, title]);
|
||||
return Promise.resolve([url, title, type]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the URL outputted in the previous function, and gets the full link (i.e. URI included).
|
||||
*
|
||||
* @param {[!URL, string]} url - Holds long URL
|
||||
* @param {[!URL, string, string]} url - Holds long URL
|
||||
* title - Title of the website
|
||||
* type - Holds the type of the request. If this is "background", the function was called from this script.
|
||||
* If this is "popup", the function was called from the popup script.
|
||||
* @returns {!Promise<ChhotoRequest, Error>}
|
||||
* If all the data was obtained, return ChhotoRequest.
|
||||
* Else, return an error
|
||||
*/
|
||||
function generateChhotoRequest([url, title]) {
|
||||
function generateChhotoRequest([url, title, type]) {
|
||||
return browser.storage.local.get().then((data) => {
|
||||
// If the user didn't specify an API key or a host
|
||||
if (!data.chhotoHost) {
|
||||
return Promise.reject(new Error(
|
||||
"Missing Chhoto URL host. Please configure the Chhoto URL extension. See https://git.solomon.tech/solomon/Chhoto-URL-Extension#installation for more information."
|
||||
));
|
||||
}
|
||||
|
||||
// If the user didn't specify an API key
|
||||
if (!data.chhotoKey) {
|
||||
return Promise.reject(new Error(
|
||||
@ -119,17 +129,19 @@ function generateChhotoRequest([url, title]) {
|
||||
));
|
||||
}
|
||||
|
||||
// If the user didn't specify an API key or a host
|
||||
if (!data.chhotoKey || !data.chhotoHost) {
|
||||
return Promise.reject(new Error("Please configure the Chhoto URL extension. See https://git.solomon.tech/solomon/Chhoto-URL-Extension#installation for more information."));
|
||||
}
|
||||
|
||||
// Set URL and title
|
||||
data.longUrl = url.href;
|
||||
|
||||
// If the request type is popup
|
||||
if (type === "popup") {
|
||||
data.title = title;
|
||||
// Normal type (i.e. generated by clicking on the extension icon once)
|
||||
} else {
|
||||
// Make the title an empty string by default
|
||||
// This will create a randomly generated string if sent to the Chhoto URL server
|
||||
data.title = "";
|
||||
}
|
||||
|
||||
// If "generateWithTitle" is true
|
||||
if (data.generateWithTitle) {
|
||||
@ -143,7 +155,8 @@ function generateChhotoRequest([url, title]) {
|
||||
let titleName = title.toLowerCase().replace(/ - /g, '-').replace(/\s+/g, '-').replace(/[^a-z0-9-_]/g, '');
|
||||
|
||||
// If the wordLimit is not 0, and thus limited
|
||||
if (wordLimit !== "0") {
|
||||
// If the type of the request does not equal "popup" (the word limit is always unlimited if it's generated from the popup page)
|
||||
if (wordLimit !== "0" && type !== "popup") {
|
||||
// Limit the length of the short URL to the configured number
|
||||
titleName = titleName.split('-', wordLimit).join('-');
|
||||
}
|
||||
@ -289,13 +302,18 @@ function notifyError(error) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main function for generating a shortened link.
|
||||
*/
|
||||
function generateChhoto() {
|
||||
// Define the type of the request
|
||||
const type = "background";
|
||||
|
||||
// Call functions
|
||||
browser.tabs
|
||||
.query({ active: true, currentWindow: true })
|
||||
.then(tabData => validateURL(new URL(tabData[0].url), tabData[0].title))
|
||||
.then(tabData => validateURL(new URL(tabData[0].url), tabData[0].title), type)
|
||||
.then(generateChhotoRequest)
|
||||
.then(requestChhoto)
|
||||
.then(validateChhotoResponse)
|
||||
@ -304,5 +322,29 @@ function generateChhoto() {
|
||||
.catch(notifyError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate a shortened link via the popup script (popup.js)
|
||||
*/
|
||||
function popupGenerateChhoto(url, title) {
|
||||
validateURL(url, title, "popup")
|
||||
.then(generateChhotoRequest)
|
||||
.then(requestChhoto)
|
||||
.then(validateChhotoResponse)
|
||||
.then(copyLinkToClipboard)
|
||||
.then(notifySuccess)
|
||||
.catch(notifyError);
|
||||
};
|
||||
|
||||
// When the extension icon is clicked, call the function above
|
||||
browser.browserAction.onClicked.addListener(generateChhoto);
|
||||
|
||||
// Create a context menu
|
||||
browser.contextMenus.create({
|
||||
title: "Manually generate a Chhoto URL",
|
||||
contexts: ["all"]
|
||||
});
|
||||
|
||||
// Run code when the context menu is clicked
|
||||
browser.contextMenus.onClicked.addListener( () => {
|
||||
browser.windows.create({url: "/popup/popup.html", type: "popup"});
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Chhoto URL",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"description": "An unofficial extension for shortening URLs using the Chhoto URL API. Requires a Chhoto URL instance.",
|
||||
"icons": {
|
||||
"16": "icons/chhoto-url-16.png",
|
||||
@ -16,10 +16,11 @@
|
||||
"notifications",
|
||||
"clipboardWrite",
|
||||
"storage",
|
||||
"https://*/*"
|
||||
"https://*/*",
|
||||
"contextMenus"
|
||||
],
|
||||
"options_ui": {
|
||||
"page": "options.html"
|
||||
"page": "options/options.html"
|
||||
},
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
@ -35,7 +36,7 @@
|
||||
"background": {
|
||||
"scripts": [
|
||||
"lib/browser-polyfill.min.js",
|
||||
"background.js"
|
||||
"background/background.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@
|
||||
|
||||
<body>
|
||||
<div class="header">
|
||||
<img src="icons/chhoto-url-48.png"></img>
|
||||
<img src="/icons/chhoto-url-48.png"></img>
|
||||
<h1>Chhoto URL Settings</h1>
|
||||
</div>
|
||||
|
||||
@ -137,7 +137,7 @@
|
||||
<p class="note">Disabling all filters will allow a link to be created from any URL.</p>
|
||||
|
||||
<div class="settings checkbox">
|
||||
<h2>URL generation</h2>
|
||||
<h2>URL Generation</h2>
|
||||
<label>
|
||||
Create shortened links with the website's title
|
||||
<input type="checkbox" id="generate-with-title">
|
||||
@ -150,8 +150,8 @@
|
||||
<p id="message2" class="note">Inputted value is not a number.</p>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript" src="lib/browser-polyfill.min.js"></script>
|
||||
<script type="application/javascript" src="options.js"></script>
|
||||
<script type="application/javascript" src="/lib/browser-polyfill.min.js"></script>
|
||||
<script type="application/javascript" src="/options/options.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
124
popup/popup.html
Normal file
124
popup/popup.html
Normal file
@ -0,0 +1,124 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
height: 300px;
|
||||
font-size: 20px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header img {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
border: black 1px solid;
|
||||
border-radius: 7px;
|
||||
background-color: white;
|
||||
min-height: 30px;
|
||||
min-width: 75px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#generate {
|
||||
border: black 1px solid;
|
||||
border-radius: 7px;
|
||||
background-color: white;
|
||||
min-height: 50px;
|
||||
min-width: 200px;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
#close:hover,
|
||||
#generate:hover {
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
.generate {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
label {
|
||||
padding: 0 10px;
|
||||
align-items: baseline;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
input {
|
||||
min-width: 150px;
|
||||
min-height: 17px;
|
||||
padding: 5px;
|
||||
border: 2px solid black;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#message,
|
||||
#message2,
|
||||
#message3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: red;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.shown {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<img src="/icons/chhoto-url-48.png"></img>
|
||||
<h1>Generate a Chhoto URL</h1>
|
||||
</div>
|
||||
|
||||
<button id="close" type="button">✖ Close</button>
|
||||
|
||||
<div class="generate">
|
||||
<label>
|
||||
Short URL
|
||||
<input required type="text" id="shorturl" placeholder="interesting-short-url">
|
||||
<p id="message"></p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="generate">
|
||||
<label>
|
||||
Long URL
|
||||
<input required type="text" id="longurl" placeholder="https://example.com">
|
||||
<p id="message2">The provided URL is invalid.</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="generate">
|
||||
<label>
|
||||
<input id="generate" type="button" value="Shorten!">
|
||||
<p id="message3">Error: Required fields are missing.</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="application/javascript" src="/lib/browser-polyfill.min.js"></script>
|
||||
<script type="application/javascript" src="/popup/popup.js"></script>
|
||||
</body>
|
||||
</html>
|
179
popup/popup.js
Normal file
179
popup/popup.js
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* An unofficial extension for easy link shortening using the Chhoto URL API.
|
||||
* Copyright (C) 2025 Solomon Tech
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* File Overview
|
||||
* @file This file handles the logic behind the popup page. This script will call a "background.js" function to generate a shortened URL.
|
||||
* @copyright Solomon Tech 2025
|
||||
*/
|
||||
|
||||
|
||||
// Use JavaScript's "strict" mode
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
|
||||
"use strict";
|
||||
|
||||
// Get elements
|
||||
const closeEle = document.querySelector("#close");
|
||||
const shortURLEle = document.querySelector("#shorturl");
|
||||
const longURLEle = document.querySelector("#longurl");
|
||||
const messageEle = document.querySelector("#message");
|
||||
const message2Ele = document.querySelector("#message2");
|
||||
const message3Ele = document.querySelector("#message3");
|
||||
const generateEle = document.querySelector("#generate");
|
||||
|
||||
// Define values
|
||||
let shorturl;
|
||||
let longurl;
|
||||
|
||||
async function close() {
|
||||
try {
|
||||
const windowId = (await browser.windows.getCurrent()).id;
|
||||
await browser.windows.remove(windowId);
|
||||
} catch (error) {
|
||||
console.log("Closing failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// If the close button is clicked, close the window
|
||||
closeEle.addEventListener('click', () => {
|
||||
close();
|
||||
});
|
||||
|
||||
// When the short URL is inputted
|
||||
shortURLEle.oninput = (event) => {
|
||||
if (event.type === "click") {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
// If the short URL value is greater than 0 (i.e. not an empty string)
|
||||
if (shortURLEle.value.length > 0) {
|
||||
// Get the browser storage
|
||||
browser.storage.local.get().then((data) => {
|
||||
// Set the word limit
|
||||
const wordLimit = data.titleWordLimit;
|
||||
|
||||
// Get the configured host
|
||||
const chhotoHost = data.chhotoHost;
|
||||
|
||||
// Format the short URL
|
||||
// Replace all occurences of ' - ' to '-'
|
||||
// Replace all occurences of ' ' to '-'
|
||||
// Replace all characters except for 'a-z', '0-9', '-' and '_' with ''
|
||||
let shortURLText = shortURLEle.value.toLowerCase().replace(/ - /g, '-').replace(/\s+/g, '-').replace(/[^a-z0-9-_]/g, '');
|
||||
|
||||
// If the wordLimit is not 0, and thus limited
|
||||
// And if the configured host is not undefined
|
||||
if (chhotoHost !== undefined) {
|
||||
// Inform the user
|
||||
messageEle.innerHTML = `The short URL will be generated as: ${chhotoHost}/${shortURLText}.`;
|
||||
} else {
|
||||
// If the configured host is undefined
|
||||
messageEle.innerHTML = "The Chhoto URL host has not been configured. Cannot generate shortened URL.";
|
||||
}
|
||||
|
||||
// Set the short URL
|
||||
shorturl = shortURLText;
|
||||
|
||||
// Show the message
|
||||
messageEle.classList.add("shown");
|
||||
});
|
||||
} else {
|
||||
// If the short URL is an empty string, hide the message
|
||||
messageEle.classList.remove("shown");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// When the long URL is inputted
|
||||
longURLEle.oninput = (event) => {
|
||||
if (event.type === "click") {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
// If the long URL value is greater than 0 (i.e. not an empty string)
|
||||
if (longURLEle.value.length > 0) {
|
||||
// Get the allowed protocols
|
||||
browser.storage.local.get("allowedProtocols").then(({ allowedProtocols }) => {
|
||||
// Initialize a list of protocols that are allowed if unset.
|
||||
if (allowedProtocols === undefined) {
|
||||
allowedProtocols = new Set();
|
||||
allowedProtocols.add("http:");
|
||||
allowedProtocols.add("https:");
|
||||
allowedProtocols.add("ftp:");
|
||||
allowedProtocols.add("file:");
|
||||
browser.storage.local.set({ allowedProtocols: Array(...allowedProtocols) });
|
||||
} else {
|
||||
allowedProtocols = new Set(allowedProtocols);
|
||||
}
|
||||
|
||||
// Try and catch structure
|
||||
try {
|
||||
// Define the URL
|
||||
const url = new URL(longURLEle.value);
|
||||
|
||||
// Ensure the URL has a valid protocol
|
||||
if (allowedProtocols.size > 0 && !allowedProtocols.has(url.protocol)) {
|
||||
throw new Error("The URL is invalid");
|
||||
};
|
||||
|
||||
////// If anything beyond this point is trigerred, the URL protocol is valid. //////
|
||||
|
||||
// Reassign the long url
|
||||
longurl = url;
|
||||
|
||||
// Remove any warning styles which may be active
|
||||
message2Ele.classList.remove("warning");
|
||||
longURLEle.style.color = "black";
|
||||
} catch {
|
||||
// If the URL is invalid, add warning styles
|
||||
message2Ele.classList.add("warning");
|
||||
longURLEle.style.color = "red";
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
} else {
|
||||
// If the long URL is an empty string, hide the warning styling
|
||||
message2Ele.classList.remove("warning");
|
||||
longURLEle.style.color = "black";
|
||||
}
|
||||
};
|
||||
|
||||
// Define the sendRequest function (which calls a background.js function to generate a shortened link)
|
||||
function sendRequest(page) {
|
||||
page.popupGenerateChhoto(longurl, shorturl);
|
||||
close();
|
||||
}
|
||||
|
||||
// If the generate button was clicked
|
||||
generateEle.addEventListener("click", () => {
|
||||
// Ensure both fields have been filled out, and the long URL is valid
|
||||
if ( shorturl !== undefined && longurl !== undefined && !message2Ele.classList.contains("warning") ) {
|
||||
|
||||
// Remove the warning class
|
||||
message3Ele.classList.remove("warning");
|
||||
|
||||
// Get the background page, and call the sendRequest function (which is in this script)
|
||||
const backgroundFunc = browser.runtime.getBackgroundPage();
|
||||
backgroundFunc.then(sendRequest);
|
||||
} else {
|
||||
// Add the warning class
|
||||
message3Ele.classList.add("warning");
|
||||
};
|
||||
});
|
Loading…
Reference in New Issue
Block a user