v1.2.0 - Add manual URL generation

This commit is contained in:
Solninja A 2025-01-27 14:25:00 +10:00
parent 888138e93b
commit ebe07d8f8b
7 changed files with 387 additions and 39 deletions

View File

@ -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 (![Chhoto URL extension icon](icons/chhoto-url-16.png)). If the extension is not pinned, the Chhoto URL extension icon will be under this highlighted icon:
![Firefox generic extension icon](https://git.solomon.tech/solomon/Chhoto-URL-Extension/raw/branch/main/generic-extension-icon-firefox.png)
![Firefox generic extension icon](https://raw.githubusercontent.com/SolninjaA/Chhoto-URL-Extension/888138e93ba31bd4eabcb9ea3f261d4381520e9b/generic-extension-icon-firefox.png)
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:
![Chromium generic extension icon](https://git.solomon.tech/solomon/Chhoto-URL-Extension/raw/branch/main/generic-extension-icon-chromium.png)
![Chromium generic extension icon](https://raw.githubusercontent.com/SolninjaA/Chhoto-URL-Extension/888138e93ba31bd4eabcb9ea3f261d4381520e9b/generic-extension-icon-chromium.png)
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 (![Chhoto URL extension icon](icons/chhoto-url-16.png))
@ -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

View File

@ -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"});
});

View File

@ -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"
]
}
}

View File

@ -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
View 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">&#10006; 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
View 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");
};
});