diff --git a/README.md b/README.md index 0edcb9f..059abf2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,54 @@ # URL_shortener_Userscript +Le projet fournit maintenant une **WebExtension Firefox** qui affiche un **petit icône dans la barre d’URL** (page action) sur les pages `http(s)`. + +## Fonctionnalités + +- Petit icône directement dans la barre d’URL Firefox. +- Clic sur l’icône → popup de raccourcissement. +- Pré-remplissage automatique avec l’URL de l’onglet actif. +- Services : **Bitly**, **TinyURL**, **Rebrandly**, **is.gd**, **v.gd**. +- Copie rapide du lien + affichage de l’URL finale. +- Page paramètres pour tokens API + historique local. +- Section de liens API et ressources d’icônes pour développeurs. + +## Structure + +- `firefox-extension/manifest.json` +- `firefox-extension/background.js` +- `firefox-extension/popup/*` +- `firefox-extension/options/*` +- `firefox-extension/icons/*` + +## Installation dans Firefox + +1. Ouvre `about:debugging#/runtime/this-firefox`. +2. Clique **Charger un module complémentaire temporaire**. +3. Sélectionne `firefox-extension/manifest.json`. +4. Ouvre n’importe quel site web (`http`/`https`), puis utilise le petit icône dans la barre d’URL. + +## Liens pour récupérer les API + +- Bitly : https://dev.bitly.com/ +- TinyURL : https://tinyurl.com/app/dev +- Rebrandly : https://developers.rebrandly.com/docs +- is.gd API : https://is.gd/apishorteningreference.php +- v.gd API : https://v.gd/apishorteningreference.php + +## Sites d’icônes pour développeurs + +- Google Material Icons : https://fonts.google.com/icons +- Heroicons : https://heroicons.com/ +- Font Awesome : https://fontawesome.com/icons +- Tabler Icons : https://tabler.io/icons + +Tu peux remplacer les icônes par défaut dans `firefox-extension/icons/`, puis ajuster les chemins dans `manifest.json`. + +## Notes + +- Bitly, TinyURL et Rebrandly nécessitent un token. +- `is.gd` et `v.gd` fonctionnent sans token. +- En mode temporaire (`about:debugging`), l’extension doit être rechargée après redémarrage de Firefox. Ce dépôt fournit maintenant une **vraie extension Firefox (WebExtension)** pour avoir un bouton dans l’interface du navigateur, utilisable sur toutes les pages web. ## ✅ Ce que fait l’extension diff --git a/firefox-extension/background.js b/firefox-extension/background.js new file mode 100644 index 0000000..d7cc382 --- /dev/null +++ b/firefox-extension/background.js @@ -0,0 +1,31 @@ +function isSupportedUrl(url) { + return typeof url === 'string' && /^https?:\/\//i.test(url); +} + +async function updatePageAction(tabId) { + try { + const tab = await browser.tabs.get(tabId); + if (tab && isSupportedUrl(tab.url)) { + await browser.pageAction.show(tabId); + } + } catch { + // ignore tabs that cannot be queried/shown + } +} + +browser.tabs.onActivated.addListener(async ({ tabId }) => { + await updatePageAction(tabId); +}); + +browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { + if (changeInfo.status === 'complete' || changeInfo.url) { + if (isSupportedUrl(tab.url)) { + await browser.pageAction.show(tabId); + } + } +}); + +browser.runtime.onInstalled.addListener(async () => { + const tabs = await browser.tabs.query({}); + await Promise.all(tabs.map((tab) => updatePageAction(tab.id))); +}); diff --git a/firefox-extension/icons/icon-16.svg b/firefox-extension/icons/icon-16.svg new file mode 100644 index 0000000..bc397f4 --- /dev/null +++ b/firefox-extension/icons/icon-16.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/firefox-extension/icons/icon-32.svg b/firefox-extension/icons/icon-32.svg new file mode 100644 index 0000000..e7650cd --- /dev/null +++ b/firefox-extension/icons/icon-32.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/firefox-extension/icons/icon-48.svg b/firefox-extension/icons/icon-48.svg new file mode 100644 index 0000000..9f5cea1 --- /dev/null +++ b/firefox-extension/icons/icon-48.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/firefox-extension/manifest.json b/firefox-extension/manifest.json index 3587566..45c9dac 100644 --- a/firefox-extension/manifest.json +++ b/firefox-extension/manifest.json @@ -1,4 +1,12 @@ { + "manifest_version": 2, + "name": "Firefox URL Shortener Hub", + "version": "1.2.0", + "description": "Raccourcir l'URL de l'onglet actif depuis une icône dans la barre d'URL Firefox.", + "permissions": [ + "storage", + "tabs", + "clipboardWrite", "manifest_version": 3, "name": "Firefox URL Shortener Hub", "version": "1.0.0", @@ -17,13 +25,30 @@ "https://v.gd/*", "" ], - "action": { + "background": { + "scripts": ["background.js"] + }, + "page_action": { "default_title": "URL Shortener", - "default_popup": "popup/popup.html" + "default_popup": "popup/popup.html", + "default_icon": { + "16": "icons/icon-16.svg", + "32": "icons/icon-32.svg" + }, + "show_matches": [ + "http://*/*", + "https://*/*" + ] }, "options_ui": { "page": "options/options.html", - "open_in_tab": true + "open_in_tab": true, + "browser_style": true + }, + "icons": { + "16": "icons/icon-16.svg", + "32": "icons/icon-32.svg", + "48": "icons/icon-48.svg" }, "browser_specific_settings": { "gecko": { diff --git a/firefox-extension/options/options.css b/firefox-extension/options/options.css index a72e818..2b7a118 100644 --- a/firefox-extension/options/options.css +++ b/firefox-extension/options/options.css @@ -1,16 +1,24 @@ body { font-family: Inter, system-ui, Arial, sans-serif; - background: #f7f7fb; + background: radial-gradient(circle at top, #f0f4ff, #f7f8fc 45%); margin: 0; + color: #1f2a44; } .container { - max-width: 760px; + max-width: 920px; margin: 24px auto; + padding: 0 12px 24px; + display: grid; + gap: 12px; +} +.panel { background: #fff; - border: 1px solid #dde0ee; - border-radius: 12px; - padding: 20px; + border: 1px solid #dde3f2; + border-radius: 14px; + padding: 18px; + box-shadow: 0 8px 20px rgba(20, 36, 80, 0.06); } +h1, h2 { margin: 0 0 8px; } label { display: block; margin: 12px 0 4px; @@ -19,29 +27,27 @@ label { input { width: 100%; box-sizing: border-box; - padding: 9px; - border: 1px solid #ccd0df; - border-radius: 8px; + padding: 10px; + border: 1px solid #ccd4e8; + border-radius: 10px; +} +input:focus { + outline: 2px solid #bfd3ff; + border-color: #739cf6; } button { - background: #0060df; + background: linear-gradient(90deg, #0059d6, #0072ff); color: #fff; border: none; - border-radius: 8px; - padding: 9px 16px; + border-radius: 10px; + padding: 10px 18px; cursor: pointer; } -.actions { - margin-top: 14px; -} -#historyList { - margin: 0; - padding-left: 18px; -} -#historyList li { - margin: 6px 0; -} -#message { - margin-top: 12px; - color: #2f6f00; -} +.actions { margin-top: 14px; } +#historyList { margin: 0; padding-left: 18px; } +#historyList li { margin: 6px 0; word-break: break-all; } +#message { margin-top: 10px; color: #0b6b12; font-weight: 600; } +a { color: #1a4ca8; text-decoration: none; } +a:hover { text-decoration: underline; } +.help { display: inline-block; margin-top: 4px; font-size: 12px; } +code { background: #eef2fb; padding: 2px 6px; border-radius: 6px; } diff --git a/firefox-extension/options/options.html b/firefox-extension/options/options.html index 79682a6..49a1a53 100644 --- a/firefox-extension/options/options.html +++ b/firefox-extension/options/options.html @@ -8,29 +8,46 @@
-

Paramètres API

-

Configure les tokens pour les services qui en ont besoin.

- - - - - - - - - - - - - -
- -
- -

Historique récent

- - -

+
+

Paramètres API

+

Configure tes tokens API pour les services premium.

+ + + + Obtenir une clé Bitly + + + + Obtenir une clé TinyURL + + + + Obtenir une clé Rebrandly + + + + +
+ +
+

+
+ +
+

Historique récent

+
    +
    + +
    +

    Icônes développeur recommandées

    + +

    Place tes icônes dans firefox-extension/icons/ et mets à jour manifest.json.

    +
    diff --git a/firefox-extension/options/options.js b/firefox-extension/options/options.js index f2260d6..7cbec7c 100644 --- a/firefox-extension/options/options.js +++ b/firefox-extension/options/options.js @@ -60,7 +60,8 @@ async function renderHistory() { history.slice(0, 20).forEach((item) => { const li = document.createElement('li'); - li.innerHTML = `${escapeHtml(item.service || 'Service')}${escapeHtml(item.shortUrl || '')}`; + const date = item.createdAt ? new Date(item.createdAt).toLocaleString('fr-FR') : ''; + li.innerHTML = `${escapeHtml(item.service || 'Service')}${escapeHtml(item.shortUrl || '')}${date ? ` (${escapeHtml(date)})` : ''}`; historyList.appendChild(li); }); } @@ -73,3 +74,11 @@ function escapeHtml(value) { .replace(/"/g, '"') .replace(/'/g, '''); } + + +document.querySelectorAll('a[target="_blank"]').forEach((link) => { + link.addEventListener('click', async (event) => { + event.preventDefault(); + await browser.tabs.create({ url: link.href }); + }); +}); diff --git a/firefox-extension/popup/popup.css b/firefox-extension/popup/popup.css index bcc0f8f..80a3fdb 100644 --- a/firefox-extension/popup/popup.css +++ b/firefox-extension/popup/popup.css @@ -1,72 +1,78 @@ :root { - color-scheme: light; font-family: Inter, system-ui, Arial, sans-serif; } body { margin: 0; - background: #f7f7fb; + background: linear-gradient(180deg, #f4f7ff 0%, #eef1fa 100%); } .card { - width: 340px; + width: 380px; box-sizing: border-box; - padding: 12px; - border: 1px solid #e2e3ed; - border-radius: 10px; + padding: 14px; + border: 1px solid #dbe0ef; + border-radius: 14px; margin: 6px; background: #fff; + box-shadow: 0 10px 24px rgba(20, 36, 80, 0.12); } .header { display: flex; justify-content: space-between; - align-items: center; + align-items: flex-start; margin-bottom: 8px; } -h1 { - font-size: 15px; - margin: 0; -} -label { - display: block; - font-size: 12px; - margin: 8px 0 4px; -} +h1 { margin: 0; font-size: 16px; } +.subtitle { margin: 2px 0 0; font-size: 11px; color: #67708a; } +label { display: block; font-size: 12px; margin: 8px 0 4px; color: #3e4761; } input, select, button { width: 100%; box-sizing: border-box; - border-radius: 8px; - border: 1px solid #cfd2e2; - padding: 8px; + border-radius: 10px; + border: 1px solid #c7cfe4; + padding: 9px; font-size: 13px; } -button { - cursor: pointer; - background: #eceef7; +input:focus, select:focus { + outline: 2px solid #bfd3ff; + border-color: #769ef6; } +button { cursor: pointer; background: #eef2fb; } .primary { - margin-top: 10px; - background: #0060df; - border-color: #0060df; + margin-top: 26px; + background: linear-gradient(90deg, #0059d6, #0072ff); color: #fff; + border: none; } .ghost { width: auto; - padding: 4px 8px; -} -.row { - display: flex; - gap: 6px; - margin-top: 8px; + padding: 5px 8px; + border-radius: 8px; } -.small { - font-size: 12px; - margin: 8px 0 0; - color: #4a4f57; +.split { + display: grid; + grid-template-columns: 1fr 126px; + gap: 8px; } +.row { display: flex; gap: 6px; margin-top: 8px; } +.small { font-size: 12px; margin: 8px 0 0; color: #4a4f57; min-height: 14px; } .result { margin-top: 10px; - border-top: 1px solid #ececf5; - padding-top: 8px; + border: 1px solid #e5e9f6; + border-radius: 10px; + padding: 9px; + background: #fafcff; } -.hidden { - display: none; +.links { + margin-top: 10px; + border-top: 1px dashed #d8dfef; + padding-top: 8px; } +.links summary { cursor: pointer; color: #2f4d88; font-size: 12px; } +.links ul { margin: 8px 0 0; padding-left: 16px; max-height: 120px; overflow: auto; } +.links li { margin: 4px 0; font-size: 12px; } +.links a { color: #1647a0; text-decoration: none; } +.links a:hover { text-decoration: underline; } +.hidden { display: none; } +#message[data-type="error"] { color: #bb0029; } +#message[data-type="success"] { color: #0b6b12; } +#message[data-type="info"] { color: #425177; } diff --git a/firefox-extension/popup/popup.html b/firefox-extension/popup/popup.html index 6c949ca..a092c67 100644 --- a/firefox-extension/popup/popup.html +++ b/firefox-extension/popup/popup.html @@ -9,23 +9,29 @@
    -

    URL Shortener

    +
    +

    URL Shortener

    +

    Icône dans la barre d’URL Firefox

    +
    - + - - - - +
    +
    + + +
    + +
    + +

    diff --git a/firefox-extension/popup/popup.js b/firefox-extension/popup/popup.js index 97091d7..5644c0c 100644 --- a/firefox-extension/popup/popup.js +++ b/firefox-extension/popup/popup.js @@ -112,6 +112,20 @@ async function init() { }); ui.expandBtn.addEventListener('click', onExpand); ui.openOptions.addEventListener('click', () => browser.runtime.openOptionsPage()); + + document.querySelectorAll('a[target="_blank"]').forEach((link) => { + link.addEventListener('click', async (event) => { + event.preventDefault(); + await browser.tabs.create({ url: link.href }); + }); + }); + + browser.storage.onChanged.addListener(async (changes, areaName) => { + if (areaName === 'sync' && changes[KEYS.settings]) { + settingsCache = await loadSettings(); + setMessage('Paramètres rechargés.', 'info'); + } + }); } async function onShorten() {