├── .gitignore ├── .jpmignore ├── README.md ├── defaults └── preferences │ └── prefs.js ├── docs ├── favicon.png ├── index.html ├── linkificator.css ├── linkificator64.png ├── linkificator_testcases.html └── linkificator_testcases.txt ├── icon48.png ├── integration ├── format.pl ├── options.full.css ├── options.full.html ├── options.full.js └── tlds.txt ├── l10n ├── linkificator-description.en-US.xml ├── linkificator-description.fr.xml └── linkificator-description.ru.xml ├── legacy ├── chrome.manifest ├── chrome │ ├── content │ │ ├── advanced-options.js │ │ ├── advanced-options.xul │ │ └── release-notes.xhtml │ ├── locale │ │ ├── en-US │ │ │ ├── advanced-options.dtd │ │ │ ├── global.properties │ │ │ ├── options.dtd │ │ │ └── release-notes.dtd │ │ ├── fr │ │ │ ├── advanced-options.dtd │ │ │ ├── global.properties │ │ │ ├── options.dtd │ │ │ └── release-notes.dtd │ │ ├── gl │ │ │ ├── advanced-options.dtd │ │ │ ├── global.properties │ │ │ ├── options.dtd │ │ │ └── release-notes.dtd │ │ └── ru │ │ │ ├── advanced-options.dtd │ │ │ ├── global.properties │ │ │ ├── options.dtd │ │ │ └── release-notes.dtd │ └── skin │ │ ├── advanced-options.css │ │ ├── arrow-circle.png │ │ ├── close.png │ │ ├── edit.png │ │ ├── empty.png │ │ ├── export.png │ │ ├── extension.png │ │ ├── favicon.png │ │ ├── icon128.png │ │ ├── icon256.png │ │ ├── icon48.png │ │ ├── icon64.png │ │ ├── import.png │ │ ├── link-exclude.png │ │ ├── link-include.png │ │ ├── link-update.png │ │ ├── link16-excluded.png │ │ ├── link16-manual.png │ │ ├── link16-off.png │ │ ├── link16-on.png │ │ ├── link32-excluded.png │ │ ├── link32-manual.png │ │ ├── link32-off.png │ │ ├── link32-on.png │ │ ├── release-notes.css │ │ ├── utilities.png │ │ └── widget.css ├── data │ ├── history.js │ ├── linkificator.js │ ├── menu │ │ └── menu.js │ ├── state.js │ ├── statistics.js │ └── utilities │ │ ├── document.js │ │ └── thread.js ├── defaults │ └── preferences │ │ └── prefs.js ├── install.rdf ├── lib │ ├── configurator.js │ ├── controler.js │ ├── main.js │ ├── ui │ │ ├── australis │ │ │ ├── menu.js │ │ │ ├── panel.js │ │ │ └── widget.js │ │ ├── file-picker.js │ │ ├── menu.js │ │ ├── panel.js │ │ ├── popup.js │ │ └── widget.js │ └── util │ │ ├── dom.js │ │ ├── system.js │ │ ├── unload.js │ │ └── windows.js ├── locale │ ├── en-US.properties │ ├── fr.properties │ ├── gl.properties │ └── ru.properties ├── options.xul └── package.json ├── main.js ├── package.json └── webextension ├── _locales ├── en_US │ └── messages.json ├── fr │ └── messages.json └── ru │ └── messages.json ├── configurator.js ├── content_scripts ├── linkificator.js └── statistics.js ├── controler.js ├── main.js ├── manifest.json ├── options ├── ShortcutCustomizeUI.js ├── advanced-options.css ├── advanced-options.html ├── advanced-options.js ├── custom-rules.js ├── delete-hover.png ├── delete.png ├── edit-hover.png ├── edit.png ├── empty.png ├── options.css ├── options.html ├── options.js └── restart.svg ├── popup ├── extension.svg ├── link-exclude.svg ├── link-include.svg ├── link-update.svg ├── popup.css ├── popup.html └── popup.js ├── resources ├── css │ └── common.css ├── doc │ ├── favicon.png │ ├── release-notes.css │ ├── release-notes.html │ └── release-notes.js ├── icons │ ├── icon128.png │ ├── icon48.png │ ├── icon64.png │ ├── link16-excluded.png │ ├── link16-manual.png │ ├── link16-off.png │ ├── link16-on.png │ ├── link32-excluded.png │ ├── link32-manual.png │ ├── link32-off.png │ └── link32-on.png └── js │ ├── DOMutils.js │ ├── localization.js │ └── thread.js └── web-ext-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.xpi 3 | web-ext-artifacts 4 | -------------------------------------------------------------------------------- /.jpmignore: -------------------------------------------------------------------------------- 1 | 2 | # temporary files 3 | *~ 4 | .DS_Store 5 | 6 | # previous xpi 7 | *.xpi 8 | 9 | # local stuff 10 | README.md 11 | legacy 12 | l10n 13 | integration 14 | NEW 15 | web-ext-artifacts 16 | 17 | # jpm stuff 18 | .jpmignore 19 | 20 | # git stuff 21 | .git 22 | .gitignore 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Linkificator 2 | ------------ 3 | 4 | This is the linkificator firefox add-on. It contains: 5 | 6 | * A program (webextension/main.js). 7 | * A module for configuration management (webextension/configurator.js) 8 | * A module for UI management (webextension/controler.js) 9 | * A module for browerAction popup (webextension/popup) 10 | * The core linkificator content script (webextension/content_scripts/linkificator.js) 11 | * Some utilities for content scripts (webextension/resources/js) 12 | * UI Options management, basic and advanced configuration (webextension/options) 13 | * [Icons by: FastIcon.com](http://www.fasticon.com) 14 | -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/docs/favicon.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Linkificator Documentation 12 | 13 | 14 | 15 | 16 | 17 |

Linkificator, a Firefox Add-on to convert text links into clickable links.

18 |
19 |
20 | This add-on parse text parts of HTML pages to match hypertext patterns not correctly encoded (i.e. not part of an anchor) and add an anchor to enable a standard mouse click to access the target as specified by the hypertext. 21 |
22 |
23 |
24 | For testing purposes, I have compiled a fairly exhaustive list of test cases. Linkificator should recognize each of these, unless otherwise marked. 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/linkificator.css: -------------------------------------------------------------------------------- 1 | 2 | .large-text { 3 | font-size: 120%; 4 | } 5 | 6 | #linkificator-icon { 7 | vertical-align: middle; 8 | float: left; 9 | margin-top: 10pt; 10 | margin-right: 30pt; 11 | margin-bottom: 10pt; 12 | margin-left: 10pt; 13 | } 14 | -------------------------------------------------------------------------------- /docs/linkificator64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/docs/linkificator64.png -------------------------------------------------------------------------------- /icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/icon48.png -------------------------------------------------------------------------------- /integration/format.pl: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | 4 | my @exts; 5 | 6 | sub uniq { 7 | my %seen; 8 | grep !$seen{$_}++, @_; 9 | } 10 | 11 | while (<>) { 12 | /\/\// and next; 13 | /^$/ and next; 14 | /^([a-z]+)$/ and push @exts, $1; 15 | /.+\.([a-z]+)$/ and push @exts, $1; 16 | } 17 | 18 | @exts = uniq (sort (@exts)); 19 | 20 | foreach (@exts) { 21 | print "$_\n"; 22 | } 23 | -------------------------------------------------------------------------------- /integration/options.full.css: -------------------------------------------------------------------------------- 1 | 2 | form { 3 | font-size: 1.25em; 4 | } 5 | 6 | .settings-entry { 7 | display: block; 8 | margin-top: 0.5em; 9 | margin-bottom: 0.5em; 10 | margin-left: 0; 11 | margin-right: 0; 12 | } 13 | 14 | .settings-entry input, select { 15 | vertical-align: top; 16 | font-size: 1em; 17 | color: #333; 18 | } 19 | .settings-entry input { 20 | border-style: solid; 21 | border-width: 1px; 22 | border-radius: 2px; 23 | border-color: #c1c1c1; 24 | } 25 | .settings-entry input:focus { 26 | border-color: rgba(0, 149, 221, 0.8); 27 | } 28 | .settings-entry > input[type="text"] { 29 | width: calc(50% - 200px); 30 | } 31 | .settings-entry > input.long-text { 32 | width: calc(95% - 200px); 33 | } 34 | .settings-entry input[type="color"] { 35 | margin-left: 15px; 36 | } 37 | .settings-entry select { 38 | border-style: solid; 39 | border-width: 1px; 40 | border-color: #c1c1c1; 41 | border-radius: 2px; 42 | } 43 | .settings-entry select:hover { 44 | background-color: #f1f1f1; 45 | } 46 | .settings-entry select option:hover { 47 | box-shadow: 0 0 10px 100px rgba(0, 149, 221, 0.25) inset ; 48 | } 49 | .settings-entry select option:checked { 50 | box-shadow: 0 0 10px 100px rgb(0, 149, 221) inset; 51 | } 52 | 53 | .settings-entry label { 54 | display: inline-block; 55 | width: 200px; 56 | margin-left: 6px; 57 | margin-right: 20px; 58 | vertical-align: top; 59 | text-align: left; 60 | } 61 | 62 | form > .settings-entry { 63 | width: 90%; 64 | } 65 | 66 | form > hr { 67 | color: #fff; 68 | width: 100%; 69 | } 70 | 71 | /*=======================================*/ 72 | .dropdown-button { 73 | font-size: 0.5em; 74 | background-color: white; 75 | color: #333; 76 | /* top right bottom left */ 77 | padding: 0px 6px 7px 8px; 78 | border-style: solid; 79 | border-width: 1px; 80 | border-color: #c1c1c1; 81 | border-radius: 2px; 82 | cursor: pointer; 83 | } 84 | 85 | .dropdown-button img { 86 | position: relative; 87 | top: 5px; 88 | left: -3px; 89 | } 90 | 91 | /* Dropdown button on hover & focus */ 92 | .dropdown-button:hover, .dropdown-button:focus { 93 | background-color: #f1f1f1; 94 | } 95 | .dropdown-button::-moz-focus-inner { 96 | border: 0; 97 | } 98 | 99 | /* The container
- needed to position the dropdown content */ 100 | .dropdown { 101 | position: relative; 102 | display: inline-block; 103 | } 104 | 105 | /* Dropdown Content (Hidden by Default) */ 106 | .dropdown-content { 107 | display: none; 108 | position: absolute; 109 | background-color: #f1f1f1; 110 | box-shadow: 4px 4px 2px 0px rgba(0,0,0,0.3); 111 | z-index: 1; 112 | min-width: 210px; 113 | } 114 | 115 | /* Entries inside the dropdown */ 116 | .dropdown-content .dropdown-entry { 117 | color: #333; 118 | padding: 4px 14px; 119 | text-decoration: none; 120 | display: inline-block; 121 | min-width: 210px; 122 | margin-left: auto; 123 | margin-right: auto; 124 | } 125 | 126 | 127 | /* Change color of dropdown entries on hover */ 128 | .dropdown-content .dropdown-entry:hover { 129 | background-color: rgba(0, 149, 221, 0.25); 130 | } 131 | 132 | /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ 133 | .dropdown-show { 134 | display: block; 135 | } 136 | 137 | .dropdown-content label { 138 | vertical-align: top; 139 | text-align: left; 140 | min-width: 120px; 141 | margin-left: 10px; 142 | width: auto; 143 | float: left; 144 | } 145 | 146 | .dropdown-content > hr { 147 | width: 100%; 148 | } 149 | .dropdown-content hr { 150 | color: #fff; 151 | float: right; 152 | margin-top: 0em; 153 | margin-bottom: 0em; 154 | } 155 | 156 | .dropdown-content input { 157 | vertical-align: top; 158 | font-size: 1.25rem; 159 | width: 16px; 160 | float: left; 161 | } 162 | .dropdown-content input[type="image"] { 163 | margin-right: 6px; 164 | } 165 | -------------------------------------------------------------------------------- /integration/options.full.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 41 |
42 |
43 | 44 | 45 |
46 |
47 |
48 | 49 | 50 | 51 |
52 |
53 | 54 | 55 | 56 |
57 |
58 |
59 | 60 | 83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /integration/options.full.js: -------------------------------------------------------------------------------- 1 | 2 | // utility function 3 | function $ (id) { 4 | return document.getElementById(id); 5 | } 6 | 7 | //=============== dropdown menu handling ===================== 8 | // to show menu on button click 9 | document.getElementById("dropdown-button").addEventListener("click", event => 10 | $("dropdown-content").classList.toggle("dropdown-show"), 11 | { 12 | capture: true 13 | }); 14 | 15 | // Close the dropdown menu if the user clicks outside of it 16 | window.addEventListener("click", event => { 17 | if (!event.target.matches('.dropdown-button')) { 18 | 19 | let dropdowns = document.getElementsByClassName("dropdown-content"); 20 | for (let element of dropdowns) { 21 | if (element.classList.contains('dropdown-show')) { 22 | element.classList.remove('dropdown-show'); 23 | } 24 | } 25 | } 26 | }); 27 | 28 | //=============== settings management ===================== 29 | function initializePreferences () { 30 | function setCheckbox (id, checked) { 31 | $(id).checked = checked; 32 | } 33 | function setColorSelector (type, config) { 34 | setCheckbox(`override-${type}-color`, config.override); 35 | let colorPicker = $(`href-${type}-color`); 36 | colorPicker.value = config.color; 37 | colorPicker.disabled = !config.override; 38 | } 39 | 40 | return browser.storage.local.get('sync').then(result => { 41 | let area = result.sync ? 'sync' : 'local'; 42 | 43 | return browser.storage[area].get().then(result => { 44 | let properties = result; 45 | properties.area = area; 46 | 47 | setCheckbox('activated', properties.activated); 48 | setCheckbox('on-demand', properties.manual); 49 | setCheckbox('display-counter', properties.displayBadge); 50 | setCheckbox('context-menu', properties.contextMenuIntegration); 51 | 52 | // domains management 53 | setCheckbox('use-regexp', properties.domains.useRegExp); 54 | $('domain-filtering-mode').value = properties.domains.type; 55 | let domainList = $('domains-list'); 56 | if (properties.domains.type == 'none') { 57 | domainList.disabled = true; 58 | domainList.value = ''; 59 | } else { 60 | domainList.disabled = false; 61 | domainList.value = properties.domains.list[properties.domains.type].join(' '); 62 | } 63 | 64 | // link colors management 65 | setColorSelector('text', properties.style.text); 66 | setColorSelector('background', properties.style.background); 67 | 68 | // settings management 69 | setCheckbox('prefs-sync', area === 'sync'); 70 | 71 | return new Promise((resolve, reject) => { 72 | resolve(properties); 73 | }); 74 | }); 75 | }); 76 | } 77 | 78 | function managePreferences (properties) { 79 | function addCheckboxManager (id, preference) { 80 | let checkbox = $(id); 81 | checkbox.addEventListener('change', event => { 82 | properties[preference] = checkbox.checked; 83 | browser.storage[properties.area].set({[preference]: properties[preference]}).catch(reason => console.error(reason)); 84 | }); 85 | } 86 | function addColorManager (type) { 87 | // checkbox 88 | let checkbox = $(`override-${type}-color`); 89 | let colorPicker = $(`href-${type}-color`); 90 | 91 | checkbox.addEventListener('change', event => { 92 | properties.style[type].override = checkbox.checked; 93 | colorPicker.disabled = !checkbox.checked; 94 | 95 | browser.storage[properties.area].set({style: properties.style}).catch(reason => console.error(reason)); 96 | }); 97 | colorPicker.addEventListener('change', event => { 98 | properties.style[type].color = colorPicker.value; 99 | 100 | browser.storage[properties.area].set({style: properties.style}).catch(reason => console.error(reason)); 101 | }); 102 | } 103 | 104 | addCheckboxManager('activated', 'activated'); 105 | addCheckboxManager('on-demand', 'manual'); 106 | addCheckboxManager('display-counter', 'displayBadge'); 107 | addCheckboxManager('context-menu', 'contextMenuIntegration'); 108 | 109 | // domain management 110 | let useRegex = $('use-regexp'); 111 | let select = $('domain-filtering-mode'); 112 | let domainList = $('domains-list'); 113 | useRegex.addEventListener('change', event => { 114 | properties.domains.useRegExp = useRegex.checked; 115 | 116 | browser.storage[properties.area].set({domains: properties.domains}).catch(reason => console.error(reason)); 117 | }); 118 | select.addEventListener('change', event => { 119 | properties.domains.type = select.value; 120 | if (select.value === 'none') { 121 | domainList.disabled = true; 122 | domainList.value = ''; 123 | } else { 124 | domainList.disabled = false; 125 | domainList.value = properties.domains.list[select.value].join(' '); 126 | } 127 | }); 128 | domainList.addEventListener('change', event => { 129 | properties.domains.list[properties.domains.type] = domainList.value.split(' '); 130 | 131 | browser.storage[properties.area].set({domains: properties.domains}).catch(reason => console.error(reason)); 132 | }); 133 | 134 | // link colors management 135 | addColorManager('text'); 136 | addColorManager('background'); 137 | 138 | // settings management 139 | let prefsSync = $('prefs-sync'); 140 | let prefsDefault = $('prefs-default'); 141 | prefsSync.addEventListener('change', event => { 142 | browser.runtime.sendMessage({id: 'change-area', sync: prefsSync.checked}).then(message => { 143 | initializePreferences(); 144 | }).catch(reason => console.error(reason)); 145 | }); 146 | prefsDefault.addEventListener('click', event => { 147 | browser.runtime.sendMessage({id: 'reset-defaults'}).then(message => { 148 | initializePreferences(); 149 | }).catch(reason => console.error(reason)); 150 | }); 151 | } 152 | 153 | 154 | document.addEventListener("DOMContentLoaded", 155 | () => { 156 | translateElements(); 157 | initializePreferences().then(properties => { 158 | managePreferences(properties); 159 | }); 160 | }, 161 | { 162 | capture: true, 163 | passive: true, 164 | once: true 165 | }); 166 | -------------------------------------------------------------------------------- /l10n/linkificator-description.en-US.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | IMPORTANT NOTICE FOR USERS OF PREVIOUS VERSIONS - PLEASE READ 13 | 14 | Linkificator was developed from the ground up as so-called WebExtension. This makes Linkificator compatible with Firefox 57 and later. Not all options of the previous version are currently available as WebExtension. As soon as Mozilla implements support for missing functionalities in Firefox, they will be integrated in a future update of Linkificator. 15 | As a result, some features are no longer available: 16 | ]]> 17 |
    18 |
  • Keyboard shortcuts
  • 19 |
  • Various Widget clicks customizations
  • 20 |
  • about: protocol support
  • 21 |
  • Import/Export of settings
  • 22 |
23 | Compatibility: The extension requires at least Firefox 55. Because the extension makes use of modern web technologies and the latest WebExtension APIs, support for older versions of Firefox is not possible for technical reasons. 25 | 26 | The current version is an hybrid version (i.e. displayed as LEGACY in Firefox) to enable to propagate current settings to the new environment. Next version will be a pure WebExtension add-on. 27 | 28 | Icons by: FastIcon.com 29 | Localization infrastructure: Crowdin.com 30 | ]]> 31 |
32 | 33 | Support E-mail on Add-on page). 35 | 36 | Help is welcomed to manage various localization. For that purpose, send-me an e-mail (see Support E-mail on Add-on page) to request translation materials. 37 | ]]> 38 | 39 | 40 |
    41 |
  • A tooltip shows which URL will be used
  • 42 |
  • The widget gives some information about treatment
  • 43 |
  • Widget menu
  • 44 |
  • The widget gives some information about treatment
  • 45 |
  • Linkificator settings
  • 46 |
  • Advanced settings, links management
  • 47 |
  • Advanced settings, custom rules management
  • 48 |
  • Advanced settings, configuration management, general settings
  • 49 |
  • Advanced settings, configuration management, top level domains
  • 50 |
51 |
52 | 53 | 56 |
    57 |
  • Short term features 58 |
      59 |
    • Add support of more locales (locales currently supported: en-US, fr and ru)
    • 60 |
    61 |
  • 62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /l10n/linkificator-description.fr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | NOTE IMPORTANTE POUR LES UTILISATEURS DES PRECEDENTES VERSIONS - A LIRE 13 | 14 | Linkificator a été entièrement re-développé en s'appuyant sur la technologie WebExtension pour permettre la compatibilité avec Firefox 57+. Certaines fonctionnalités, disponibles dans les versions précédentes, ne peuvent plus être offertes dans le cadre de WebExtension. Celles-ci seront de nouveau intégrées à Linkificator dés que Mozilla offre de nouvelles possibilités à WebExtension. 15 | Les fonctionnalités suivantes ne sont donc plus disponibles: 16 | ]]> 17 |
    18 |
  • Raccourcis clavier
  • 19 |
  • Les diverses configurations des clics sur le Widget
  • 20 |
  • support du protocole about:
  • 21 |
  • Importation/Exportation des réglages
  • 22 |
23 | Compatibilité: Ce module nécessite, au minimum, la version 55 de Firefox due à la mise en œuvre de technologies nouvelles (WebExtension). Le support des versions plus anciennes n'est donc pas possible pour des raisons techniques. 25 | 26 | Cette version est une version hybride (i.e. affiché comme OBSOLÈTE dans Firefox) pour permettre la transmission des réglages actuels vers le nouvel environnement. La prochaine version sera un module exclusivement basé sur l'API WebExtension. 27 | 28 | Icones fournies par FastIcon.com 29 | Infrastructure de localisation : Crowdin.com 30 | ]]> 31 |
32 | 33 | Courriel d'assistance sur la page du module) plutôt que de poster un avis. 35 | 36 | Toute aide pour la localisation est la bienvenue. Pour cela, il suffit de m'envoyer un courriel (voir Courriel d'assistance sur la page du module) pour demander les éléments nécessaires à la localisation. 37 | ]]> 38 | 39 | 40 |
    41 |
  • Une info-bulle montre quel URL sera utilisé
  • 42 |
  • Le widget fournit des informations concernant le traitement
  • 43 |
  • Menu du widget
  • 44 |
  • Le widget fournit des informations concernant le traitement
  • 45 |
  • Réglages généraux
  • 46 |
  • Réglages avancés, gestion des liens
  • 47 |
  • Réglages avancés, gestion des règles utilisateur
  • 48 |
  • Réglages avancés, gestion de la configuration, paramètres généraux
  • 49 |
  • Réglages avancés, gestion de la configuration, listes des domaines de premier niveau
  • 50 |
51 |
52 | 53 | 56 |
    57 |
  • Fonctionnalités court terme 58 |
      59 |
    • Ajout de nouvelles localisations (locales actuellement supportées : en-US, fr et ru)
    • 60 |
    61 |
  • 62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /l10n/linkificator-description.ru.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | ВАЖНОЕ ЗАМЕЧАНИЕ ДЛЯ ПОЛЬЗОВАТЕЛЕЙ ПРЕДЫДУЩИХ ВЕРСИЙ - ПОЖАЛУЙСТА, ПРОЧИТАЙТЕ 13 | 14 | Linkificator был разработан с нуля, как так называемый WebExtension. Это делает Linkificator совместимым с Firefox 57 и более поздними версиями. Не все опции предыдущей версии в настоящее время доступны для WebExtension. Как только Mozilla реализует поддержку отсутствующих функциональных возможностей в Firefox, они будут интегрированы в будущие версии Linkificator. 15 | В результате некоторые функции больше недоступны: 16 | ]]> 17 |
    18 |
  • Горячие клавиши
  • 19 |
  • Различные настройки виджета
  • 20 |
  • Поддержка about: протокола
  • 21 |
  • Импорт/Экспорт настроек
  • 22 |
23 | Совместимость: Для расширения требуется Firefox 55 или более поздняя версия. Поскольку расширение использует современные веб-технологии и последние API-интерфейсы WebExtension, поддержка старых версий Firefox невозможна по техническим причинам. 25 | 26 | Текущая версия представляет собой гибридную версию (т.е. отображаемую как УСТАРЕВШЕЕ в Firefox), чтобы включить применение текущих настроек в новой среде. Следующая версия будет чистым дополнением WebExtension. 27 | 28 | Значки: FastIcon.com 29 | Система локализации: Crowdin.com 30 | ]]> 31 |
32 | 33 | Адрес эл. почты поддержки на странице дополнения). 36 | 37 | Приветствуется помощь по созданию различных локализаций. Для этого отправьте разработчику письмо по электронной почте (см. Адрес эл. почты поддержки на странице дополнения) для запроса локализуемых материалов. 38 | ]]> 39 | 40 | 41 |
    42 |
  • Подсказка показывает, какой URL будет использоваться
  • 43 |
  • Виджет предоставляет некоторую информацию об обработке
  • 44 |
  • Меню виджета
  • 45 |
  • Виджет предоставляет некоторую информацию об обработке
  • 46 |
  • Настройки Linkificator
  • 47 |
  • Расширенные настройки, управление ссылками
  • 48 |
  • Расширенные настройки, управление пользовательскими правилами
  • 49 |
  • Расширенные настройки, управления конфигурацией, общие настройки
  • 50 |
  • Расширенные настройки, управление конфигурацией, домены верхнего уровня
  • 51 |
52 |
53 | 54 | 58 |
    59 |
  • Функции в краткосрочной перспективе 60 |
      61 |
    • Добавить поддержку нескольких языков (в настоящее время поддерживаются языки: en-US, fr и ru)
    • 62 |
    63 |
  • 64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /legacy/chrome.manifest: -------------------------------------------------------------------------------- 1 | 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | # chrome manifest - Linkificator's module 7 | # author: MarkaPola 8 | 9 | content linkificator chrome/content/ 10 | skin linkificator classic/1.0 chrome/skin/ 11 | 12 | locale linkificator en-US chrome/locale/en-US/ 13 | locale linkificator fr chrome/locale/fr/ 14 | locale linkificator ru chrome/locale/ru/ 15 | -------------------------------------------------------------------------------- /legacy/chrome/locale/en-US/advanced-options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /legacy/chrome/locale/en-US/global.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # en-US translation - Advanced Settings module 8 | # author: MarkaPola 9 | 10 | advanced-settings.rules-validation.title=Custom Rule Validation 11 | advanced-settings.rules-validation.empty-field=Fields must not be empty 12 | advanced-settings.rules-validation.invalid-re=Errors on regular expressions 13 | advanced-settings.rules-validation.invalid-pattern=Pattern is not a valid regular expression 14 | advanced-settings.rules-validation.invalid-url=URL is not a valid regular expression 15 | -------------------------------------------------------------------------------- /legacy/chrome/locale/en-US/options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /legacy/chrome/locale/fr/advanced-options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /legacy/chrome/locale/fr/global.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # fr translation - Advanced Settings module 8 | # author: MarkaPola 9 | 10 | advanced-settings.rules-validation.title=Validation r\u00e8gle utilisateur 11 | advanced-settings.rules-validation.empty-field=Les champs ne doivent pas \u00eatre vides 12 | advanced-settings.rules-validation.invalid-re=Erreurs dans les espressions r\u00e9guli\u00e8res 13 | advanced-settings.rules-validation.invalid-pattern=L'expression r\u00e9guli\u00e8re pour le mod\u00e8le est invalide 14 | advanced-settings.rules-validation.invalid-url=L'expression r\u00e9guli\u00e8re pour l'URL est invalide 15 | 16 | -------------------------------------------------------------------------------- /legacy/chrome/locale/fr/options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /legacy/chrome/locale/gl/advanced-options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /legacy/chrome/locale/gl/global.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # gl translation - Advanced Settings module 8 | # author: 9 | 10 | advanced-settings.rules-validation.title=Personalizar regra de validaci\u00f3n 11 | advanced-settings.rules-validation.empty-field=Os campos non deben estar baleiros 12 | advanced-settings.rules-validation.invalid-re=Erros nas expresi\u00f3ns regulares 13 | advanced-settings.rules-validation.invalid-pattern=Pattern is not a valid regular expression 14 | advanced-settings.rules-validation.invalid-url=URL is not a valid regular expression 15 | 16 | -------------------------------------------------------------------------------- /legacy/chrome/locale/gl/options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /legacy/chrome/locale/ru/advanced-options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /legacy/chrome/locale/ru/global.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # ru translation - Advanced Settings module 8 | # author: Капелька Яда 9 | 10 | advanced-settings.rules-validation.title=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u0430 11 | advanced-settings.rules-validation.empty-field=\u041f\u043e\u043b\u044f \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c\u0438 12 | advanced-settings.rules-validation.invalid-re=\u041e\u0448\u0438\u0431\u043a\u0438 \u0432 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u0445 \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u0445 13 | advanced-settings.rules-validation.invalid-pattern=\u0428\u0430\u0431\u043b\u043e\u043d \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u043c \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u043c \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c 14 | advanced-settings.rules-validation.invalid-url=URL-\u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u043c \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u044b\u043c \u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c 15 | 16 | -------------------------------------------------------------------------------- /legacy/chrome/locale/ru/options.dtd: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /legacy/chrome/skin/advanced-options.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * This Source Code is subject to the terms of the Mozilla Public License 4 | * version 2.0 (the "License"). You can obtain a copy of the License at 5 | * http://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); 9 | @namespace html url("http://www.w3.org/1999/xhtml"); 10 | 11 | #advanced-settings { 12 | -moz-appearance: window !important; 13 | } 14 | 15 | #advanced-settings\.tabbox { 16 | width: 500px; 17 | } 18 | 19 | .advanced-settings\.custom-rules\.edit-button { 20 | border-style: none !important; 21 | -moz-user-focus: normal; 22 | list-style-image: url("edit.png"); 23 | -moz-appearance: none; 24 | -moz-image-region: rect(0px, 14px, 14px, 0px); 25 | } 26 | 27 | .advanced-settings\.custom-rules\.edit-button:hover { 28 | -moz-image-region: rect(0px, 28px, 14px, 14px); 29 | } 30 | 31 | .advanced-settings\.custom-rules\.delete-button { 32 | border-style: none !important; 33 | -moz-user-focus: normal; 34 | list-style-image: url("close.png"); 35 | -moz-appearance: none; 36 | -moz-image-region: rect(0px, 14px, 14px, 0px); 37 | } 38 | 39 | .advanced-settings\.custom-rules\.delete-button:hover { 40 | -moz-image-region: rect(0px, 28px, 14px, 14px); 41 | } 42 | 43 | #advanced-settings\.custom-rules\.panel { 44 | width: 450px; 45 | } 46 | .advanced-settings\.custom-rules\.panel\.textbox { 47 | width: 320px; 48 | } 49 | 50 | .advanced-settings\.configuration\.textbox { 51 | width: 330px; 52 | } 53 | -------------------------------------------------------------------------------- /legacy/chrome/skin/arrow-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/arrow-circle.png -------------------------------------------------------------------------------- /legacy/chrome/skin/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/close.png -------------------------------------------------------------------------------- /legacy/chrome/skin/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/edit.png -------------------------------------------------------------------------------- /legacy/chrome/skin/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/empty.png -------------------------------------------------------------------------------- /legacy/chrome/skin/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/export.png -------------------------------------------------------------------------------- /legacy/chrome/skin/extension.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/extension.png -------------------------------------------------------------------------------- /legacy/chrome/skin/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/favicon.png -------------------------------------------------------------------------------- /legacy/chrome/skin/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/icon128.png -------------------------------------------------------------------------------- /legacy/chrome/skin/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/icon256.png -------------------------------------------------------------------------------- /legacy/chrome/skin/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/icon48.png -------------------------------------------------------------------------------- /legacy/chrome/skin/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/icon64.png -------------------------------------------------------------------------------- /legacy/chrome/skin/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/import.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link-exclude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link-exclude.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link-include.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link-include.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link-update.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link16-excluded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link16-excluded.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link16-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link16-manual.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link16-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link16-off.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link16-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link16-on.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link32-excluded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link32-excluded.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link32-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link32-manual.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link32-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link32-off.png -------------------------------------------------------------------------------- /legacy/chrome/skin/link32-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/link32-on.png -------------------------------------------------------------------------------- /legacy/chrome/skin/release-notes.css: -------------------------------------------------------------------------------- 1 | 2 | .large-text { 3 | font-size: 120%; 4 | } 5 | 6 | .version-changes { 7 | position: relative; 8 | left: 5%; 9 | } 10 | 11 | .feature-list-title { 12 | font-size: 115%; 13 | font-weight: bold; 14 | color: rgb(30, 100, 255); 15 | } 16 | 17 | html { 18 | background: linear-gradient(to bottom, rgb(220, 245, 255), rgb(255, 255, 255)); 19 | } 20 | 21 | #linkificator-icon { 22 | vertical-align: middle; 23 | float: left; 24 | margin-top: 10pt; 25 | margin-right: 30pt; 26 | margin-bottom: 10pt; 27 | margin-left: 10pt; 28 | } 29 | -------------------------------------------------------------------------------- /legacy/chrome/skin/utilities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/legacy/chrome/skin/utilities.png -------------------------------------------------------------------------------- /legacy/chrome/skin/widget.css: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | * 6 | * widget style-sheet - Linkificator's module 7 | * author: MarkaPola 8 | */ 9 | 10 | @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); 11 | 12 | @-moz-document url("chrome://browser/content/browser.xul") { 13 | .linkificator-on { 14 | list-style-image: url("chrome://linkificator/skin/link16-on.png"); 15 | } 16 | /* Added for Australis support */ 17 | .linkificator-on[cui-areatype="menu-panel"], 18 | toolbarpaletteitem[place="palette"] > .linkificator-on { 19 | list-style-image: url("chrome://linkificator/skin/link32-on.png"); 20 | } 21 | 22 | .linkificator-manual { 23 | list-style-image: url("chrome://linkificator/skin/link16-manual.png"); 24 | } 25 | /* Added for Australis support */ 26 | .linkificator-manual[cui-areatype="menu-panel"], 27 | toolbarpaletteitem[place="palette"] > .linkificator-manual { 28 | list-style-image: url("chrome://linkificator/skin/link32-manual.png"); 29 | } 30 | 31 | .linkificator-off { 32 | list-style-image: url("chrome://linkificator/skin/link16-off.png"); 33 | } 34 | /* Added for Australis support */ 35 | .linkificator-off[cui-areatype="menu-panel"], 36 | toolbarpaletteitem[place="palette"] > .linkificator-off { 37 | list-style-image: url("chrome://linkificator/skin/link32-off.png"); 38 | } 39 | 40 | .linkificator-excluded { 41 | list-style-image: url("chrome://linkificator/skin/link16-excluded.png"); 42 | } 43 | /* Added for Australis support */ 44 | .linkificator-excluded[cui-areatype="menu-panel"], 45 | toolbarpaletteitem[place="palette"] > .linkificator-excluded { 46 | list-style-image: url("chrome://linkificator/skin/link32-excluded.png"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /legacy/data/history.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* tab history handling - Linkificator's module 7 | * author: MarkaPola */ 8 | 9 | // catch backward/forward button events to handle widget update 10 | function toPage (event) { 11 | if (event.persisted) { 12 | self.port.emit('pageshow'); 13 | } 14 | } 15 | 16 | self.port.on('attach', function () { 17 | window.top.addEventListener('pageshow', toPage, false); 18 | }); 19 | -------------------------------------------------------------------------------- /legacy/data/menu/menu.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | 7 | /* panel state and mouse clicks handling - Linkificator's module 8 | * author: MarkaPola */ 9 | 10 | self.on("context", function (node) { 11 | var state = State(document); 12 | 13 | return state.isConfigured() || state.isComplete(); 14 | }); 15 | 16 | self.on('click', function (node, data) { 17 | self.postMessage(data); 18 | }); 19 | -------------------------------------------------------------------------------- /legacy/data/state.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* state parsing management - Linkificator's module 7 | * author: MarkaPola */ 8 | 9 | function State (document) { 10 | "use strict"; 11 | 12 | const statusLabel = "linkificator-status"; 13 | 14 | var body = document.body; 15 | 16 | // if (action == 'parse' && body.hasAttribute(statusLabel) && body.getAttribute(statusLabel) != 'configured') { 17 | // // parsing is already in process or done 18 | // return null; 19 | // } 20 | // if (action == 're-parse' && (!body.hasAttribute(statusLabel) || (body.getAttribute(statusLabel) != "complete" && body.getAttribute(statusLabel) != "configured"))) { 21 | // // parsing is not yet started or is in process 22 | // return null; 23 | // } 24 | 25 | // if (action == 'undo' && !body.hasAttribute(statusLabel)) { 26 | // // parsing is not yet started 27 | // return null; 28 | // } 29 | 30 | // if (action == 'reset') { 31 | // if (body.hasAttribute(statusLabel)) { 32 | // body.removeAttribute(statusLabel); 33 | // } 34 | // return null; 35 | // } 36 | 37 | //body.setAttribute(statusLabel, action == 'undo' ? "in-undo" : "in-process"); 38 | 39 | return { 40 | isValid: function (action) { 41 | if (action == 'parse' && body.hasAttribute(statusLabel) && body.getAttribute(statusLabel) != 'configured') { 42 | // parsing is already in process or done 43 | return false; 44 | } 45 | if (action == 're-parse' && (!body.hasAttribute(statusLabel) || (body.getAttribute(statusLabel) != "complete" && body.getAttribute(statusLabel) != "configured"))) { 46 | // parsing is not yet started or is in process 47 | return false; 48 | } 49 | 50 | if (action == 'undo' && !body.hasAttribute(statusLabel)) { 51 | // parsing is not yet started 52 | return false; 53 | } 54 | 55 | return true; 56 | }, 57 | 58 | process: function () { 59 | body.setAttribute(statusLabel, "in-process"); 60 | }, 61 | inProcess: function () { 62 | return body.hasAttribute(statusLabel) 63 | && body.getAttribute(statusLabel) == "in-process"; 64 | }, 65 | 66 | configured: function () { 67 | body.setAttribute(statusLabel, "configured"); 68 | }, 69 | isConfigured: function () { 70 | return body.hasAttribute(statusLabel) 71 | && body.getAttribute(statusLabel) == "configured"; 72 | }, 73 | 74 | complete: function () { 75 | body.setAttribute(statusLabel, "complete"); 76 | }, 77 | isComplete: function() { 78 | return body.hasAttribute(statusLabel) 79 | && body.getAttribute(statusLabel) == "complete"; 80 | }, 81 | 82 | undo: function () { 83 | body.setAttribute(statusLabel, "in-undo"); 84 | }, 85 | 86 | reset: function () { 87 | if (body.hasAttribute(statusLabel)) { 88 | body.removeAttribute(statusLabel); 89 | } 90 | } 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /legacy/data/statistics.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* Statistics management - Linkificator's module 7 | * author: MarkaPola */ 8 | 9 | 10 | function Statistics (document, action) { 11 | "use strict"; 12 | 13 | const countLabel = "linkificator-count"; 14 | const timeLabel = "linkificator-time"; 15 | 16 | var body = document.body; 17 | 18 | function getInt (value) { 19 | let v = parseInt(value, 10); 20 | return isNaN(v) ? 0 : v; 21 | } 22 | 23 | function getStats (count, time) { 24 | return {links: getInt(count), time: getInt(time)}; 25 | } 26 | 27 | if (action == 'undo') { 28 | if (body.hasAttribute(countLabel)) { 29 | body.removeAttribute(countLabel); 30 | body.removeAttribute(timeLabel); 31 | } 32 | return null; 33 | } 34 | 35 | if (action == 'get-statistics') { 36 | return { 37 | get: function () { 38 | if (body.hasAttribute(countLabel)) { 39 | return getStats(body.getAttribute(countLabel), 40 | body.getAttribute(timeLabel)); 41 | } else { 42 | return getStats(0, 0); 43 | } 44 | } 45 | }; 46 | } 47 | 48 | var elapse = 0; 49 | var startTime = Date.now(); 50 | 51 | if (action === 'parse') { 52 | body.setAttribute(countLabel, 0); 53 | body.setAttribute(timeLabel, 0); 54 | } else if (action == 're-parse') { 55 | elapse = getInt(body.getAttribute(timeLabel)); 56 | } 57 | 58 | return { 59 | store: function (count) { 60 | let links = getInt(body.getAttribute(countLabel)); 61 | 62 | body.setAttribute(countLabel, links+count); 63 | body.setAttribute(timeLabel, elapse+(Date.now()-startTime)); 64 | }, 65 | 66 | get: function () { 67 | if (body.hasAttribute(countLabel)) { 68 | return getStats(body.getAttribute(countLabel), 69 | body.getAttribute(timeLabel)); 70 | } else { 71 | return getStats(0, 0); 72 | } 73 | } 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /legacy/data/utilities/document.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // dom.js - Linkificator's module 7 | // author: MarkaPola 8 | // 9 | 10 | // 11 | // DOcument tools 12 | // 13 | 14 | /* 15 | * Retrieve MIME type from DOM Document 16 | * 17 | * @param [object] document: DOM Document object 18 | */ 19 | 20 | function Document (doc) { 21 | "use strict"; 22 | 23 | var document = doc; 24 | 25 | return { 26 | get contentType () { 27 | let contentType = null; 28 | 29 | if (document) { 30 | // specify multiple CSS Selectors because 'i' flag is not supported before FF47 31 | let ct = document.querySelector('meta[http-equiv="content-type"], meta[http-equiv="Content-Type"], meta[http-equiv="Content-type"], meta[http-equiv="content-Type"]'); 32 | if (ct) { 33 | let content = ct.getAttribute('content'); 34 | if (content) { 35 | contentType = content.trim().split(/;|\s/)[0]; 36 | } 37 | } 38 | 39 | if (! contentType) { 40 | // Check possible plain text page 41 | if (document.querySelector('link[href="resource://gre-resources/plaintext.css"]')) { 42 | contentType = 'text/plain'; 43 | } 44 | } 45 | } 46 | 47 | if (! contentType) { 48 | contentType = 'text/html'; 49 | } 50 | 51 | return contentType; 52 | } 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /legacy/data/utilities/thread.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* Thread helper - Linkificator's module 7 | * author: MarkaPola */ 8 | 9 | // javascript does not support parallel executions 10 | // to avoid any CPU total comsumption and UI freezes, this utility help execute tasks in the background 11 | 12 | // arguments to Thread function are: 13 | // action: object which MUST expose, at least, the following functions: 14 | // * execute: will execute a part of the work and must return true if work is completed 15 | // * finish: will execute all remaining tasks to complete the work 16 | // * abort: thread was aborted, remaining work must not be done 17 | // * complete: this function will be called when all work is done 18 | // interval: waiting time (in ms) between each execution. If not specified, 10ms will be used. 19 | // all functions are argumentless except abort and complete which pass thread reference. 20 | // 21 | // Thread functions are: 22 | // start: launch execution of action object in the background. 23 | // terminate: request to complete action in the foreground. 24 | // kill: stop background execution. action will not terminate. 25 | // 26 | // Here is a small example: each part of the work will be done at 100ms interval. 27 | // 28 | // var test = { 29 | // index: 0, 30 | // total: 100, 31 | // 32 | // execute: function () { 33 | // var part = Math.min((this.index + 3), this.total); 34 | // while (this.index < part) { 35 | // console.log (this.index); 36 | // this.index++; 37 | // } 38 | // console.log ("chunk done"); 39 | // 40 | // return this.index == this.total; 41 | // }, 42 | // finish: function () { 43 | // while (this.index++ < this.total) 44 | // console.log (this.index); 45 | // console.log ("complete done"); 46 | // }, 47 | // abort: function (thread) { 48 | // console.log ("aborted at " + this.index); 49 | // }, 50 | // complete: function (thread) { 51 | // console.log ("completed"); 52 | // } 53 | // }; 54 | // 55 | // var thread = Thread (test, 100); 56 | // thread.start (); 57 | // 58 | 59 | 60 | function Thread (action, interval) { 61 | "use strict"; 62 | 63 | var thread = { 64 | ref: null, 65 | interval: 10, 66 | action: null, 67 | worker: null, 68 | completed: false 69 | }; 70 | if (interval) 71 | thread.interval = interval; 72 | thread.action = action; 73 | 74 | function execute () { 75 | if (thread.completed) 76 | return; 77 | 78 | if (thread.action.execute()) { 79 | thread.completed = true; 80 | thread.action.complete(thread.ref); 81 | } else { 82 | thread.worker = setTimeout(function(){execute();}, interval); 83 | } 84 | } 85 | 86 | function finish (timeout) { 87 | if (thread.completed) 88 | return; 89 | 90 | clearTimeout(thread.worker); 91 | 92 | let terminate = function() { 93 | thread.completed = true; 94 | thread.action.finish(); 95 | thread.action.complete(thread.ref); 96 | }; 97 | 98 | if (timeout) { 99 | thread.worker = setTimeout(terminate, timeout); 100 | } else { 101 | terminate(); 102 | } 103 | } 104 | 105 | function abort () { 106 | if (thread.completed) 107 | return; 108 | 109 | clearTimeout(thread.worker); 110 | 111 | thread.completed = true; 112 | thread.action.abort(thread.ref); 113 | } 114 | 115 | thread.ref = { 116 | start: function () { 117 | execute(); 118 | }, 119 | 120 | terminate: function (timeout) { 121 | finish(); 122 | }, 123 | 124 | kill: function () { 125 | abort(); 126 | } 127 | }; 128 | 129 | return thread.ref; 130 | } 131 | -------------------------------------------------------------------------------- /legacy/install.rdf: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | linkificator@markapola 8 | 2.3.2 9 | 2 10 | true 11 | false 12 | true 13 | 14 | chrome://linkificator/skin/icon48.png 15 | 16 | 17 | 18 | 19 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 20 | 38.0 21 | 47.* 22 | 23 | 24 | 25 | 26 | Linkificator 27 | 28 | 2 29 | Converts text links into clickable links... 30 | MarkaPola 31 | Icons by: FastIcon.com (http://www.fasticon.com) 32 | Localization infrastructure: Crowdin.com (http://crowdin.com) 33 | MarkaPola 34 | 35 | 36 | 37 | en-US 38 | MarkaPola 39 | Icons from www.fasticon.com 40 | Localization infrastructure: Crowdin.com (http://crowdin.com) 41 | MarkaPola 42 | 43 | Linkificator 44 | Converts text links into clickable links... 45 | 46 | 47 | 48 | 49 | fr 50 | MarkaPola 51 | Icones fournies par www.fasticon.com 52 | Infrastructure de localisation : Crowdin.com (http://crowdin.com) 53 | MarkaPola 54 | 55 | Linkificator 56 | Transforme les liens textuels en liens hypertexte cliquables... 57 | 58 | 59 | 60 | 61 | ru 62 | MarkaPola 63 | Icons from www.fasticon.com 64 | Localization infrastructure: Crowdin.com (http://crowdin.com) 65 | Капелька Яда 66 | 67 | Linkificator 68 | Преобразует текстовые ссылки в кликабельные. 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /legacy/lib/main.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // The main module of the Linkificator Add-on. 7 | // author: MarkaPola 8 | 9 | var Context = {}; 10 | 11 | // workers management 12 | var workers = []; 13 | workers.apply = function (tab, action) { 14 | this.forEach (function (worker, index, array) { 15 | if (worker.tab === tab) { 16 | try { 17 | action (worker, index, array); 18 | } catch (e) { 19 | // exception could be raised if history is used because 20 | // some workers are attached to a hidden page, so ERR_FROZEN is raised 21 | } 22 | } 23 | }); 24 | }; 25 | workers.detach = function (worker) { 26 | var index = this.indexOf(worker); 27 | if(index != -1) { 28 | this.splice(index, 1); 29 | } 30 | }; 31 | 32 | 33 | exports.main = function (options) { 34 | "use strict"; 35 | 36 | var prefs = require('sdk/preferences/service'); 37 | var pageMod = require('sdk/page-mod'); 38 | var tabs = require('sdk/tabs'); 39 | 40 | var data = require('sdk/self').data; 41 | 42 | // for new installation or upgrade, show an informational page 43 | if (options.loadReason == 'install' || options.loadReason == 'upgrade') { 44 | require('sdk/timers').setTimeout(function() { 45 | tabs.open("chrome://linkificator/content/release-notes.xhtml"); 46 | }, 2000); 47 | } 48 | 49 | var configurator = require('./configurator').Configurator(); 50 | var controler = require('./controler').Controler(configurator); 51 | Context = {configurator: configurator, controler: controler}; 52 | 53 | function Statistics () { 54 | this.links = 0; 55 | this.time = 0; 56 | } 57 | 58 | // process specified tab 59 | function process (action, tab) { 60 | workers.apply(tab, function (worker) { 61 | let properties = configurator.properties; 62 | if (tab.contentType) { 63 | properties.document = {contentType: tab.contentType}; 64 | } else { 65 | properties.document = {contentType: null}; 66 | } 67 | 68 | worker.port.emit(action, properties); 69 | }); 70 | } 71 | 72 | tabs.on('activate', function (tab) { 73 | let processTab = function(tab) { 74 | tab.removeListener('ready', processTab); 75 | 76 | controler.setStatus(tab); 77 | 78 | // re-launch valid action 79 | if (controler.isValidDocument(tab) && controler.linkifyURL(tab)) { 80 | process(controler.isActive() ? 'parse' : 'undo', tab); 81 | } 82 | }; 83 | 84 | let worker = tab.attach({contentScript: "self.port.emit ('readyState', document.readyState && (document.readyState == 'interactive' || document.readyState == 'complete'));"}); 85 | 86 | worker.port.on('readyState', function(ready) { 87 | if (ready) { 88 | processTab(tab); 89 | } else { 90 | tab.on('ready', processTab); 91 | } 92 | }); 93 | }); 94 | tabs.on('ready', function (tab) { 95 | controler.setStatus(tab); 96 | }); 97 | 98 | // to handle page history browsing 99 | pageMod.PageMod({ 100 | include: ["*", "file://*"], 101 | attachTo: ["existing", "top"], 102 | contentScriptWhen: 'start', 103 | contentScriptFile: data.url("history.js"), 104 | 105 | onAttach: function (worker) { 106 | let tab = worker.tab; 107 | 108 | worker.port.on('pageshow', function () { 109 | controler.setStatus(tab); 110 | 111 | if (controler.isValidDocument(tab) && controler.linkifyURL(tab)) { 112 | process(controler.isActive() ? 'parse' : 'undo', tab); 113 | } 114 | }); 115 | 116 | worker.port.emit('attach'); 117 | } 118 | }); 119 | 120 | // parsing of all frames 121 | pageMod.PageMod({ 122 | include: ["*", "file://*"], 123 | attachTo: ["existing", "top", "frame"], 124 | contentScriptWhen: 'end', 125 | contentScriptFile: [data.url("utilities/thread.js"), data.url("utilities/document.js"), 126 | data.url("statistics.js"), data.url("state.js"), 127 | data.url("linkificator.js")], 128 | 129 | onAttach: function (worker) { 130 | let tab = worker.tab; 131 | 132 | if (!tab) { 133 | // not content script attached to worker 134 | return; 135 | } 136 | 137 | let properties = configurator.properties; 138 | 139 | if (tab.contentType) { 140 | properties.document = {contentType: tab.contentType}; 141 | tab.linkificator = {contentType: tab.contentType, statistics: null}; 142 | } else { 143 | properties.document = {contentType: null}; 144 | tab.linkificator = {contentType: 'text/html', statistics: null}; 145 | } 146 | 147 | controler.setStatus(tab); 148 | 149 | if (!controler.isValidDocument(tab)) { 150 | return; 151 | } 152 | 153 | // store worker to enable to retrieve statistics and re-parsing on add-on re-activation 154 | workers.push(worker); 155 | worker.on('detach', function () { 156 | workers.detach(this); 157 | }); 158 | 159 | worker.port.on('content-type', function (data) { 160 | tab.linkificator.contentType = data; 161 | properties.document.contentType = data; 162 | 163 | controler.setStatus(tab); 164 | }); 165 | 166 | worker.port.on('document-changed', function () { 167 | if (workers.indexOf(worker) !== -1) { 168 | controler.setStatus(tab); 169 | worker.port.emit(controler.linkifyURL(tab) ? 're-parse' : 'undo', properties); 170 | } 171 | }); 172 | 173 | worker.port.on('statistics', function (data) { 174 | let statistics = tab.linkificator.statistics; 175 | 176 | statistics.links += data.links; 177 | statistics.time = Math.max(statistics.time, data.time); 178 | controler.setStatistics(tab, statistics); 179 | }); 180 | 181 | worker.port.on('open-url', function (action) { 182 | if (action.button === 'left') { 183 | tabs.activeTab.url = action.url; 184 | } else { 185 | tabs.open({ 186 | url: action.url, 187 | inBackground: prefs.get('browser.tabs.loadInBackground', false) 188 | }); 189 | } 190 | }); 191 | 192 | if (controler.isActive() && controler.linkifyURL(tab)) { 193 | worker.port.emit('initial-parse', properties); 194 | } 195 | } 196 | }); 197 | 198 | // compute statistics 199 | controler.on('statistics', function (tab) { 200 | if (controler.isValidDocument(tab) && controler.linkifyURL(tab)) { 201 | tab.linkificator.statistics = new Statistics; 202 | 203 | workers.apply(tab, function (worker) { 204 | worker.port.emit('get-statistics'); 205 | }); 206 | } 207 | }); 208 | 209 | // to re-parse current tab on user's request 210 | controler.on('re-parse', function (tab) { 211 | process('re-parse', tab); 212 | }); 213 | // to ensure linkification on add-on reactivation through widget or keyboard shortcut 214 | // or inclusion of previously excluded url 215 | controler.on('activate', function (tab) { 216 | process('parse', tab); 217 | }); 218 | // to revert all linkificator changes 219 | controler.on('undo', function (tab) { 220 | process('undo', tab); 221 | }); 222 | }; 223 | 224 | exports.onUnload = function (reason) { 225 | "use strict"; 226 | 227 | if (reason == 'disable' || reason == 'uninstall') { 228 | // reload tabs to get unmodified content 229 | let tabs = []; 230 | 231 | for (let worker of workers) { 232 | if (tabs.indexOf(worker.tab) == -1) 233 | tabs.push(worker.tab); 234 | } 235 | 236 | for (let tab of tabs) { 237 | tab.reload(); 238 | } 239 | } 240 | }; 241 | -------------------------------------------------------------------------------- /legacy/lib/ui/australis/menu.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // australis/menu.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage Australis UI elements 12 | // 13 | 14 | 15 | "use strict"; 16 | 17 | const {Cu} = require('chrome'); 18 | const {ShortcutUtils} = Cu.import('resource://gre/modules/ShortcutUtils.jsm'); 19 | 20 | const { toJSON: jsonify } = require("sdk/keyboard/utils"); 21 | 22 | const dom = require('../../util/dom'); 23 | const panels = require('./panel'); 24 | 25 | 26 | function MenuEvent (nodes) 27 | { 28 | let menu = {}; 29 | 30 | for (let id in nodes) { 31 | let entry = nodes[id]; 32 | // add management interface for each menu entry 33 | menu[id] = { 34 | get node () { 35 | return entry; 36 | }, 37 | 38 | set label (data) { 39 | entry.setAttribute('label', data); 40 | }, 41 | set image (data) { 42 | entry.setAttribute('image', data); 43 | }, 44 | set checked (data) { 45 | if (data) 46 | entry.setAttribute('checked', 'true'); 47 | }, 48 | set shortcut (data) { 49 | let hotkey = jsonify(data); 50 | let keyNode = entry.ownerDocument.createElementNS(dom.NAMESPACES.xul, 'key'); 51 | keyNode.setAttribute('modifiers', hotkey.modifiers.join(' ')); 52 | keyNode.setAttribute('key', hotkey.key); 53 | entry.setAttribute('shortcut', ShortcutUtils.prettifyShortcut(keyNode)); 54 | }, 55 | get disabled () { 56 | let data = entry.getAttribute('disabled'); 57 | return (data && data === 'true'); 58 | }, 59 | set disabled (data) { 60 | if (data) 61 | entry.setAttribute('disabled', 'true'); 62 | else 63 | entry.removeAttribute('disabled'); 64 | } 65 | }; 66 | } 67 | 68 | return menu; 69 | } 70 | 71 | const MenuTrait = function (options) { 72 | if (!options) return; 73 | 74 | this.menu = null; 75 | 76 | this.handlers = { 77 | show: options.onShow, 78 | hide: options.onHide 79 | }; 80 | 81 | this.onShow = function (event, nodes) { 82 | if (this.handlers.show) 83 | this.handlers.show(MenuEvent(nodes)); 84 | }.bind(this); 85 | this.onHide = function (event, nodes) { 86 | if (this.handlers.hide) 87 | this.handlers.hide(MenuEvent(nodes)); 88 | }.bind(this); 89 | 90 | if (options.onShow) 91 | options.onShow = this.onShow; 92 | if (options.onHide) 93 | options.onHide = this.onHide; 94 | 95 | this.panel = panels.Panel(options); 96 | }; 97 | 98 | exports.Menu = function (options) { 99 | var mt = new MenuTrait(options); 100 | 101 | return { 102 | get panel () { 103 | return mt.panel; 104 | }, 105 | 106 | on: function (event, action) { 107 | if (event === 'show') { 108 | mt.handlers.show = action; 109 | } else if (event === 'hide') { 110 | mt.handlers.hide = action; 111 | } 112 | }, 113 | 114 | destroy: function () { 115 | mt.panel.destroy(); 116 | } 117 | }; 118 | }; 119 | -------------------------------------------------------------------------------- /legacy/lib/ui/australis/panel.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // australis/panel.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage Australis UI elements 12 | // 13 | 14 | 15 | "use strict"; 16 | 17 | const unload = require('../../util/unload').unload; 18 | const watchWindows = require('../../util/windows').watchWindows; 19 | const DOMGenerator = require('../../util/dom'); 20 | 21 | const PanelTrait = function (options) { 22 | if (!options) return; 23 | 24 | this.options = options; 25 | 26 | this.handlers = { 27 | show: options.onShow, 28 | hide: options.onHide 29 | }; 30 | 31 | this.onShow = function (event, nodes) { 32 | if (this.handlers.show) 33 | this.handlers.show(event, nodes); 34 | }.bind(this); 35 | this.onHide = function (event, nodes) { 36 | if (this.handlers.hide) 37 | this.handlers.hide(event, nodes); 38 | }.bind(this); 39 | 40 | this.views = new Set(); 41 | }; 42 | 43 | function createView (trait, document) { 44 | function onShow (event) { 45 | trait.onShow(event, nodes); 46 | } 47 | function onHide (event) { 48 | trait.onHide(event, nodes); 49 | } 50 | 51 | var nodes = {}; 52 | var view = DOMGenerator.fromJSON(trait.options.content, document, nodes); 53 | trait.views.add(view); 54 | 55 | view.setAttribute("id", trait.options.id); 56 | 57 | document.getElementById("PanelUI-multiView").appendChild(view); 58 | unload(function () { 59 | document.getElementById("PanelUI-multiView").removeChild(view); 60 | trait.views.delete(view); 61 | }, document); 62 | 63 | view.addEventListener('ViewShowing', onShow, false); 64 | unload(function () { view.removeEventListener('ViewShowing', onShow, false); }, view); 65 | 66 | view.addEventListener('ViewHiding', onHide, false); 67 | unload(function () { view.removeEventListener('ViewHiding', onHide, false); }, view); 68 | } 69 | 70 | exports.Panel = function (options) { 71 | // check validity of properties 72 | if (!options.id) { 73 | throw new Error("id option is required"); 74 | } 75 | if (!options.content) { 76 | throw new Error("content option is required"); 77 | } 78 | 79 | let pt = new PanelTrait(options); 80 | 81 | function watcher (window) { 82 | createView(pt, window.document); 83 | } 84 | watchWindows(watcher); 85 | 86 | return { 87 | id: options.id, 88 | 89 | on: function (event, action) { 90 | if (event === 'show') { 91 | pt.handlers.show = action; 92 | } else if (event === 'hide') { 93 | pt.handlers.hide = action; 94 | } 95 | }, 96 | 97 | destroy: function () { 98 | pt.views.forEach(function (view) { 99 | let document = view.ownerDocument; 100 | document.getElementById("PanelUI-multiView").removeChild(view); 101 | }); 102 | pt.views.clear(); 103 | } 104 | }; 105 | }; 106 | 107 | -------------------------------------------------------------------------------- /legacy/lib/ui/australis/widget.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // australis/widget.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage Australis UI elements 12 | // 13 | 14 | "use strict"; 15 | 16 | const { Cu, Cc, Ci } = require('chrome'); 17 | const { CustomizableUI } = Cu.import("resource:///modules/CustomizableUI.jsm"); 18 | 19 | const windows = require("sdk/windows"); 20 | const window_utils = require('sdk/window/utils'); 21 | const stylesheets = require('sdk/stylesheet/utils'); 22 | 23 | const unload = require('../../util/unload').unload; 24 | const watchWindows = require('../../util/windows').watchWindows; 25 | 26 | var { versionCompare } = require('../../util/system'); 27 | 28 | var windowManager = (function () { 29 | const view = require('sdk/view/core'); 30 | const model = require('sdk/model/core'); 31 | 32 | return { 33 | viewFor: function (window) { 34 | return view.viewFor(window); 35 | }, 36 | modelFor: function (window) { 37 | return model.modelFor(window); 38 | } 39 | }; 40 | })(); 41 | 42 | 43 | const WidgetTrait = function (options) { 44 | if (!options) return; 45 | 46 | function loadStyleSheet (window) { 47 | stylesheets.loadSheet(window, options.stylesheet, 'user'); 48 | 49 | unload((function () { stylesheets.removeSheet(window, options.stylesheet, 'user'); }).bind(this), 50 | window); 51 | } 52 | 53 | this.widget = null; 54 | this.widgetNodes = new Map(); 55 | 56 | this.handlers = { 57 | mouseover: options.onMouseover, 58 | click: options.onClick, 59 | middleclick: options.onMiddleclick, 60 | rightclick: options.onRightclick 61 | }; 62 | 63 | this.onMouseover = function (event) { 64 | if (this.handlers.mouseover) 65 | this.handlers.mouseover(windowManager.modelFor(window_utils.getOwnerBrowserWindow(event.target)), event); 66 | }.bind(this); 67 | 68 | this.woptions = { 69 | onCreated: (function (node) { 70 | if (options.icon) { 71 | node.classList.add(options.icon); 72 | this.widgetNodes.set(node, {icon: options.icon}); 73 | } 74 | else 75 | this.widgetNodes.set(node, {icon: ''}); 76 | 77 | node.addEventListener('mouseover', this.onMouseover); 78 | unload((function () {node.removeEventListener('mouseover', this.onMouseover);}).bind(this), node); 79 | }).bind(this), 80 | 81 | onClick: (function (event) { 82 | if (this.handlers.click) { 83 | this.handlers.click(windowManager.modelFor(window_utils.getOwnerBrowserWindow(event.target)), event); 84 | event.stopPropagation(); 85 | event.preventDefault(); 86 | 87 | return; 88 | } 89 | 90 | // middle-click 91 | if (event.button == 1 || (event.button == 0 && event.altKey == true)) { 92 | if (this.handlers.middleclick) { 93 | this.handlers.middleclick(windowManager.modelFor(window_utils.getOwnerBrowserWindow(event.target)), event); 94 | event.stopPropagation(); 95 | event.preventDefault(); 96 | } 97 | } 98 | // right-click 99 | if (event.button == 2 || (event.button == 0 && event.shiftKey == true)) { 100 | if (this.handlers.rightclick) { 101 | this.handlers.rightclick(windowManager.modelFor(window_utils.getOwnerBrowserWindow(event.target)), event); 102 | event.stopPropagation(); 103 | event.preventDefault(); 104 | } 105 | } 106 | }).bind(this) 107 | }; 108 | 109 | if (options.panel) { 110 | this.woptions.viewId = options.panel.id; 111 | } 112 | if (options.tooltip) { 113 | this.woptions.tooltiptext = options.tooltip; 114 | } 115 | if (options.stylesheet) { 116 | watchWindows(loadStyleSheet); 117 | } 118 | this.woptions.defaultArea = options.defaultArea ? options.defaultArea : CustomizableUI.AREA_NAVBAR; 119 | // populate woptions from options for other options 120 | var validOptions = ['id', 'type', 'label', 'removable', 'overflows', 'shortcutId', 'showInPrivateBrowsing']; 121 | validOptions.forEach((function (option) { 122 | if (options[option]) 123 | this.woptions[option] = options[option]; 124 | }).bind(this)); 125 | 126 | // connect some listeners 127 | let listener = { 128 | onWidgetInstanceRemoved: (function (id, document) { 129 | if (id !== options.id) { 130 | return; 131 | } 132 | 133 | this.widgetNodes.delete(document.getElementById(id)); 134 | }).bind(this) 135 | }; 136 | CustomizableUI.addListener(listener); 137 | 138 | this.destroyWidget = function () { 139 | this.widgetNodes.clear(); 140 | CustomizableUI.removeListener(listener); 141 | CustomizableUI.destroyWidget(this.woptions.id); 142 | }; 143 | unload((function () { this.destroyWidget(); }).bind(this)); 144 | 145 | this.widget = CustomizableUI.createWidget(this.woptions); 146 | }; 147 | 148 | exports.Widget = function (options) { 149 | // check validity of properties 150 | if (options.type === 'view') { 151 | if (!options.panel) { 152 | throw new Error("panel option required for view type"); 153 | } 154 | } else { 155 | if (options.panel) { 156 | throw new Error("panel option only valid for view type"); 157 | } 158 | } 159 | 160 | let wt = new WidgetTrait(options); 161 | 162 | return { 163 | on: function (event, action) { 164 | if (event === 'mouseover') { 165 | wt.handlers.mouseover = action; 166 | } else if (event === 'click') { 167 | wt.handlers.click = action; 168 | } else if (event === 'middleclick') { 169 | wt.handlers.middleclick = action; 170 | } else if (event === 'rightclick') { 171 | wt.handlers.rightclick = action; 172 | } 173 | }, 174 | 175 | getView: function (window) { 176 | let win = windowManager.viewFor(window); 177 | let instance = null; 178 | try { 179 | instance = win ? wt.widget.forWindow(win) : null; 180 | } catch (e) { 181 | // can be called too early: no document attached to window 182 | } 183 | if (!instance) return null; 184 | 185 | return { 186 | set icon (value) { 187 | if (instance) { 188 | let config = wt.widgetNodes.get(instance.node); 189 | if (value !== config.icon) { 190 | instance.node.classList.add(value); 191 | instance.node.classList.remove(config.icon); 192 | 193 | config.icon = value; 194 | } 195 | } 196 | }, 197 | 198 | set tooltip (value) { 199 | if (instance) { 200 | instance.node.tooltipText = value; 201 | } 202 | } 203 | }; 204 | }, 205 | 206 | set icon (value) { 207 | wt.widget.instances.forEach(function (instance) { 208 | let config = wt.widgetNodes.get(instance.node); 209 | 210 | if (value !== config.icon) { 211 | instance.node.classList.add(value); 212 | instance.node.classList.remove(config.icon); 213 | 214 | config.icon = value; 215 | } 216 | }); 217 | }, 218 | set tooltip (value) { 219 | wt.widget.instances.forEach(function (instance) { 220 | instance.node.tooltipText = value; 221 | }); 222 | }, 223 | 224 | destroy: function () { 225 | wt.destroyWidget(); 226 | } 227 | }; 228 | }; 229 | 230 | // exports some useful constonts from CustomizableUI 231 | exports.AREA_PANEL = CustomizableUI.AREA_PANEL; 232 | exports.AREA_NAVBAR = CustomizableUI.AREA_NAVBAR; 233 | exports.AREA_MENUBAR = CustomizableUI.AREA_MENUBAR; 234 | exports.AREA_TABSTRIP = CustomizableUI.AREA_TABSTRIP; 235 | exports.AREA_BOOKMARKS = CustomizableUI.AREA_BOOKMARKS; 236 | -------------------------------------------------------------------------------- /legacy/lib/ui/file-picker.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // file-picker.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage file picker UI 12 | // 13 | 14 | "use strict"; 15 | 16 | const {Cu, Cc, Ci} = require('chrome'); 17 | const {Services} = Cu.import('resource://gre/modules/Services.jsm'); 18 | const {FileUtils} = Cu.import('resource://gre/modules/FileUtils.jsm'); 19 | const nsIFilePicker = Ci.nsIFilePicker; 20 | 21 | 22 | exports.show = function (window, mode, callback, properties) { 23 | var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); 24 | 25 | function fpCallback (result) { 26 | if (result != nsIFilePicker.returnCancel) 27 | callback(fp.file.path); 28 | } 29 | 30 | var fpMode = mode == "open" ? nsIFilePicker.modeOpen : nsIFilePicker.modeSave; 31 | 32 | if (properties.directory) { 33 | if (properties.directory == "") { 34 | fp.displayDirectory = Services.dirsvc.get("Home", Ci.nsIFile); 35 | } else { 36 | fp.displayDirectory = new FileUtils.File(properties.directory); 37 | } 38 | } 39 | 40 | if (mode == "save" && properties.filename) 41 | fp.defaultString = properties.filename; 42 | 43 | if (properties.extension) { 44 | fp.defaultExtension = properties.extension; 45 | fp.appendFilter(properties.extension+" Files", "*."+properties.extension); 46 | } 47 | fp.appendFilters(fp.filterText+fp.filterAll); 48 | 49 | fp.init(window, properties.title, fpMode); 50 | 51 | fp.open(fpCallback); 52 | }; 53 | -------------------------------------------------------------------------------- /legacy/lib/ui/menu.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // menu.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage High Level UI elements 12 | // 13 | 14 | "use strict"; 15 | 16 | var menus = require('./australis/menu'); 17 | 18 | 19 | exports.Menu = function (options) { 20 | return menus.Menu(options); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /legacy/lib/ui/panel.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // panel.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage High Level UI elements 12 | // 13 | 14 | "use strict"; 15 | 16 | var panels = require('./australis/panel'); 17 | 18 | exports.Panel = function (options) { 19 | return panels.Panel(options); 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /legacy/lib/ui/popup.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // popup.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage Chrome UI elements 12 | // 13 | 14 | "use strict"; 15 | 16 | const {Cu} = require('chrome'); 17 | const {Services} = Cu.import('resource://gre/modules/Services.jsm'); 18 | 19 | 20 | exports.display = function (id, xul, parameters) { 21 | let settingsWindow = Services.wm.getMostRecentWindow(id); 22 | if (settingsWindow) { 23 | settingsWindow.focus(); 24 | } else { 25 | parameters.wrappedJSObject = parameters; 26 | 27 | Services.ww.openWindow(null, xul, "_blank", "chrome,centerscreen,dialog=yes,modal=yes,titlebar=yes", parameters); 28 | } 29 | }; 30 | 31 | /** 32 | * Shows an alert message like window.alert() but with a custom title. 33 | * 34 | * @param {Window} parentWindow parent window of the dialog (can be null) 35 | * @param {String} title dialog title 36 | * @param {String} message message to be displayed 37 | */ 38 | exports.alert = function (parentWindow, title, message) { 39 | if (!title) 40 | title = "Linkificator"; 41 | Services.prompt.alert(parentWindow, title, message); 42 | }; 43 | 44 | /** 45 | * Shows a dialog message with OK and Cancel buttons. 46 | * 47 | * @param {Window} parentWindow parent window of the dialog (can be null) 48 | * @param {String} title dialog title 49 | * @param {String} message message to be displayed 50 | */ 51 | exports.confirm = function (parentWindow, title, message) { 52 | if (!title) 53 | title = "Linkificator"; 54 | return Services.prompt.confirm(parentWindow, title, message); 55 | }; 56 | -------------------------------------------------------------------------------- /legacy/lib/ui/widget.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // widget.js - Linkificator's module 7 | // author: MarkaPola 8 | 9 | 10 | // 11 | // Manage High Level UI elements 12 | // 13 | 14 | "use strict"; 15 | 16 | var widgets = require('./australis/widget'); 17 | 18 | exports.Widget = function (options) { 19 | // check validity of properties 20 | if (options.panel && options.menu) { 21 | throw new Error("panel and menu options are mutually exclusive"); 22 | } 23 | 24 | // handle options 25 | if (options.menu) { 26 | options.panel = options.menu.panel; 27 | } 28 | 29 | return widgets.Widget(options); 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /legacy/lib/util/dom.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // dom.js - Linkificator's module 7 | // author: MarkaPola 8 | // 9 | 10 | // 11 | // DOM tools 12 | // 13 | 14 | const NAMESPACES = { 15 | html: "http://www.w3.org/1999/xhtml", 16 | xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 17 | }; 18 | 19 | var defaultNamespace = NAMESPACES.xul; 20 | 21 | exports.NAMESPACES = Object.freeze(NAMESPACES); 22 | exports.defaultNamespace = exports.NAMESPACES.xul; 23 | 24 | /* 25 | * Generates a Document fragment from a JSON structure 26 | * 27 | * @param [object] json: JSON structure describing DOM to generate 28 | * @param [object] document: DOM object used to generate new elements 29 | * @output [array] nodes: list of nodes with attribute 'key' in JSON description 30 | */ 31 | function fromJSON (json, document, nodes) { 32 | const unload = require('./unload').unload; 33 | 34 | function namespace(name) { 35 | var m = /^(?:(.*):)?(.*)$/.exec(name); 36 | return [NAMESPACES[m[1]], m[2]]; 37 | } 38 | 39 | function tag(name, attr) { 40 | if (Array.isArray(name)) { 41 | var frag = document.createDocumentFragment(); 42 | Array.forEach(arguments, function (arg) { 43 | if (!Array.isArray(arg[0])) 44 | frag.appendChild(tag.apply(null, arg)); 45 | else 46 | arg.forEach(function (arg) { 47 | frag.appendChild(tag.apply(null, arg)); 48 | }); 49 | }); 50 | return frag; 51 | } 52 | 53 | var args = Array.slice(arguments, 2); 54 | var vals = namespace(name); 55 | var elem = document.createElementNS(vals[0] || defaultNamespace, 56 | vals[1]); 57 | 58 | for (var key in attr) { 59 | var val = attr[key]; 60 | if (nodes && key == "key") 61 | nodes[val] = elem; 62 | 63 | vals = namespace(key); 64 | if (typeof val == "function") { 65 | elem.addEventListener(key, val, false); 66 | unload (function () { elem.removeEventlistener(key, val, false); }, elem); 67 | } else 68 | elem.setAttributeNS(vals[0] || "", vals[1], val); 69 | } 70 | args.forEach(function(e) { 71 | elem.appendChild(typeof e == "object" ? tag.apply(null, e) : 72 | e instanceof Node ? e : document.createTextNode(e)); 73 | }); 74 | return elem; 75 | } 76 | return tag.apply(null, json); 77 | } 78 | 79 | exports.fromJSON = fromJSON; 80 | -------------------------------------------------------------------------------- /legacy/lib/util/system.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // system.js - Linkificator's module 6 | // author: MarkaPola 7 | // 8 | 9 | // 10 | // Manage configuration 11 | // 12 | 13 | var australis = true; 14 | 15 | try { 16 | require('chrome').Cu.import('resource:///modules/CustomizableUI.jsm', {}); 17 | } 18 | catch (e) { 19 | australis = false; 20 | } 21 | 22 | Object.defineProperty(exports, "australis", { 23 | enumerable: true, 24 | get: (function () { 25 | return australis; 26 | }).bind(this) 27 | }); 28 | 29 | 30 | function versionCompare (v1, v2) { 31 | const {Cu, Ci} = require('chrome'); 32 | const {Services} = Cu.import('resource://gre/modules/Services.jsm'); 33 | 34 | return Services.vc.compare(v1, v2); 35 | } 36 | 37 | exports.versionCompare = versionCompare; 38 | -------------------------------------------------------------------------------- /legacy/lib/util/unload.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // unload.js - Linkificator's module 6 | // author: MarkaPola 7 | // 8 | // This code is derived from work done by Mardak (see https://github.com/Mardak/restartless/blob/watchWindows/bootstrap.js) 9 | // 10 | 11 | // 12 | // Manage actions on unload 13 | // 14 | 15 | /** 16 | * Save callbacks to run when unloading. Optionally scope the callback to a 17 | * container, e.g., window. Unload will be done automatically on add-on unload. 18 | * Provide a way to run all the callbacks. 19 | * 20 | * @usage unload(): Run all callbacks and release them. 21 | * 22 | * @usage unload(callback): Add a callback to run on unload. 23 | * @param [function] callback: 0-parameter function to call on unload. 24 | * @return [function]: A 0-parameter function that undoes adding the callback. 25 | * 26 | * @usage unload(callback, container) Add a scoped callback to run on unload. 27 | * @param [function] callback: 0-parameter function to call on unload. 28 | * @param [node] container: Remove the callback when this container unloads. 29 | * @return [function]: A 0-parameter function that undoes adding the callback. 30 | */ 31 | function unload (callback, container) { 32 | // Initialize the array of unloaders and global unload on the first usage 33 | let unloaders = unload.unloaders; 34 | if (unloaders == null) { 35 | unloaders = unload.unloaders = []; 36 | 37 | // execute unload on add-on unload 38 | require('sdk/system/unload').when(function(reason) { unload(); }); 39 | } 40 | 41 | // Calling with no arguments runs all the unloader callbacks 42 | if (callback == null) { 43 | unloaders.slice().forEach(function(unloader) { unloader(); }); 44 | unloaders.length = 0; 45 | return undefined; 46 | } 47 | 48 | // The callback is bound to the lifetime of the container if we have one 49 | if (container != null) { 50 | // Remove the unloader when the container unloads 51 | container.addEventListener("unload", removeUnloader, false); 52 | 53 | // Wrap the callback to additionally remove the unload listener 54 | let origCallback = callback; 55 | callback = function() { 56 | container.removeEventListener("unload", removeUnloader, false); 57 | origCallback(); 58 | }; 59 | } 60 | 61 | // Wrap the callback in a function that ignores failures 62 | function unloader() { 63 | try { 64 | callback(); 65 | } catch(ex) {} 66 | } 67 | unloaders.push(unloader); 68 | 69 | // Provide a way to remove the unloader 70 | function removeUnloader() { 71 | let index = unloaders.indexOf(unloader); 72 | if (index != -1) 73 | unloaders.splice(index, 1); 74 | } 75 | return removeUnloader; 76 | } 77 | 78 | exports.unload = unload; 79 | -------------------------------------------------------------------------------- /legacy/lib/util/windows.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // windows.js - Linkificator's module 7 | // author: MarkaPola 8 | // 9 | // This code is derived from work done by Mardak (see https://github.com/Mardak/restartless/blob/watchWindows/bootstrap.js) 10 | // 11 | 12 | 13 | // 14 | // Manage windows objects creation 15 | // 16 | 17 | /** 18 | * Apply a callback to each open and new browser windows. 19 | * 20 | * @usage watchWindows(callback): Apply a callback to each browser window. 21 | * @param [function] callback: 1-parameter function that gets a browser window. 22 | */ 23 | function watchWindows (callback) { 24 | const {Cu, Ci} = require('chrome'); 25 | const {Services} = Cu.import('resource://gre/modules/Services.jsm'); 26 | 27 | const unload = require('./unload').unload; 28 | 29 | // Wrap the callback in a function that ignores failures 30 | function watcher (window) { 31 | try { 32 | // Now that the window has loaded, only handle browser windows 33 | let {documentElement} = window.document; 34 | if (documentElement.getAttribute("windowtype") == "navigator:browser") 35 | callback(window); 36 | } catch(ex) {} 37 | } 38 | 39 | // Wait for the window to finish loading before running the callback 40 | function runOnLoad(window) { 41 | // Listen for one load event before checking the window type 42 | window.addEventListener("DOMContentLoaded", function runOnce () { 43 | window.removeEventListener("DOMContentLoaded", runOnce, false); 44 | watcher(window); 45 | }, false); 46 | } 47 | 48 | // Add functionality to existing windows 49 | let windows = Services.wm.getEnumerator("navigator:browser"); 50 | while (windows.hasMoreElements()) { 51 | // Only run the watcher immediately if the window is completely loaded 52 | let window = windows.getNext(); 53 | if (window.document.readyState == "complete") 54 | watcher(window); 55 | // Wait for the window to load before continuing 56 | else 57 | runOnLoad(window); 58 | } 59 | 60 | let windowListener = { 61 | onOpenWindow: function (xulWindow) { 62 | let window = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); 63 | runOnLoad(window); 64 | }, 65 | 66 | onCloseWindow: function (xulWindow) {}, 67 | onWindowTitleChange: function (xulWindow) {} 68 | }; 69 | 70 | // Watch for new browser windows opening then wait for it to load 71 | Services.wm.addListener(windowListener); 72 | 73 | // Make sure to stop watching for windows if we're unloading 74 | unload(function() { Services.wm.removeListener(windowListener); }); 75 | } 76 | 77 | exports.watchWindows = watchWindows; 78 | -------------------------------------------------------------------------------- /legacy/locale/en-US.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # en-US translation - Linkificator's module 8 | # author: MarkaPola 9 | 10 | # statistics 11 | stats.links[zero]=0 links processed 12 | stats.links[one]=1 link processsed 13 | stats.links=%d links processed 14 | stats.time=in %d ms 15 | stats.excluded=page excluded 16 | stats.filtered=page ignored 17 | stats.not_processed=page not processed 18 | stats.undetermined=undetermined 19 | 20 | # widget panel 21 | panel.options=Options... 22 | panel.manual=On demand 23 | panel.enable=Enable 24 | panel.disable=Disable 25 | panel.include=Include 26 | panel.exclude=Exclude 27 | panel.linkify=Update 28 | 29 | # context menu 30 | menu.linkify=Update links 31 | 32 | # alert message 33 | alert.title=Linkificator degraded mode 34 | alert.message=Due to some Firefox SDK limitations, Linkificator context menu is not supported on this version. It is strongly recommanded to upgrade Firefox to version 21 or above. 35 | 36 | # settings management 37 | reset.title=Linkificator - Restore Default Settings 38 | reset.message=You are about to reset settings to default values.\u000aAre you sure you want to continue? 39 | 40 | export.title=Linkificator - Export Settings 41 | import.title=Linkificator - Import Settings 42 | -------------------------------------------------------------------------------- /legacy/locale/fr.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # fr translation - Linkificator's module 8 | # author: MarkaPola 9 | 10 | # statistics 11 | stats.links[zero]=0 liens trait\u00e9s 12 | stats.links[one]=1 lien trait\u00e9 13 | stats.links=%d liens trait\u00e9s 14 | stats.time=en %d ms 15 | stats.excluded=page exclue 16 | stats.filtered=page ignor\u00e9e 17 | stats.not_processed=page non trait\u00e9e 18 | stats.undetermined=ind\u00e9termin\u00e9 19 | 20 | # widget panel 21 | panel.options=Options... 22 | panel.manual=Sur demande 23 | panel.enable=Activer 24 | panel.disable=D\u00e9sactiver 25 | panel.include=Inclure 26 | panel.exclude=Exclure 27 | panel.linkify=Actualiser 28 | 29 | # context menu 30 | menu.linkify=Actualiser les liens 31 | 32 | # alert message 33 | alert.title=Linkificator en mode d\u00e9grad\u00e9 34 | alert.message=Du fait de certaines limitations du SDK de Firefox, le menu contextuel de Linkificator n'est pas support\u00e9 sur cette version. Il est fortement recommand\u00e9 de mettre \u00e0 jour Firefox vers la version 21 ou suivantes. 35 | 36 | # settings management 37 | reset.title=Linkificator - R\u00e9initialisation des r\u00e9glages 38 | reset.message=Vous \u00eates sur le point de r\u00e9initialiser les r\u00e9glages par d\u00e9faut.\u000aEtes-vous s\u00fbr de vouloir continuer? 39 | 40 | export.title=Linkificator - Exporter les r\u00e9glages 41 | import.title=Linkificator - Importer les r\u00e9glages 42 | -------------------------------------------------------------------------------- /legacy/locale/gl.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # gl translation - Linkificator's module 8 | # author: 9 | 10 | # statistics 11 | stats.links[zero]=0 ligaz\u00f3ns procesadas 12 | stats.links[one]=1 ligaz\u00f3n procesada 13 | stats.links=%d ligaz\u00f3ns procesadas 14 | stats.time=en %d m 15 | stats.excluded=p\u00e1xina exclu\u00edda 16 | stats.filtered=p\u00e1xina ignorada 17 | stats.not_processed=p\u00e1xina non procesada 18 | stats.undetermined=sen determinar 19 | 20 | # widget panel 21 | panel.options=Opci\u00f3ns... 22 | panel.manual=On Demand 23 | panel.enable=Activar 24 | panel.disable=Desactivar 25 | panel.include=Inclu\u00edr 26 | panel.exclude=Exclu\u00edr 27 | panel.linkify=Actualizar 28 | 29 | # context menu 30 | menu.linkify=Actualizar ligaz\u00f3ns 31 | 32 | # alert message 33 | alert.title=Linkificator degraded mode 34 | alert.message=Due to some Firefox SDK limitations, Linkificator context menu is not supported on this version. It is strongly recommanded to upgrade Firefox to version 21 or above. 35 | 36 | # settings management 37 | reset.title=Linkificator - Restore Default Settings 38 | reset.message=You are about to reset settings to default values.\u000aAre you sure you want to continue? 39 | 40 | export.title=Linkificator - Export Settings 41 | import.title=Linkificator - Import Settings 42 | -------------------------------------------------------------------------------- /legacy/locale/ru.properties: -------------------------------------------------------------------------------- 1 | #X-Generator: crowdin.com 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # ru translation - Linkificator's module 8 | # author: Капелька Яда 9 | 10 | # statistics 11 | stats.links[zero]=0 \u0441\u0441\u044b\u043b\u043e\u043a \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043e 12 | stats.links[one]=1 \u0441\u0441\u044b\u043b\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u0430 13 | stats.links=%d \u0441\u0441\u044b\u043b\u043e\u043a \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043e 14 | stats.time=\u0437\u0430 %d \u043c\u0441\u0435\u043a 15 | stats.excluded=\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0430 16 | stats.filtered=\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043f\u0440\u043e\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0430 17 | stats.not_processed=\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u0430 18 | stats.undetermined=\u043d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e 19 | 20 | # widget panel 21 | panel.options=\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438... 22 | panel.manual=\u041f\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0443 23 | panel.enable=\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c 24 | panel.disable=\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c 25 | panel.include=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c 26 | panel.exclude=\u0418\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u044c 27 | panel.linkify=\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c 28 | 29 | # context menu 30 | menu.linkify=\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0438 31 | 32 | # alert message 33 | alert.title=\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c Linkificator 34 | alert.message=\u0412 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f\u043c\u0438 Firefox SDK, \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435 \u043c\u0435\u043d\u044e Linkificator \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u044d\u0442\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0438. \u041d\u0430\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c Firefox \u0434\u043e \u0432\u0435\u0440\u0441\u0438\u0438 21 \u0438\u043b\u0438 \u0432\u044b\u0448\u0435. 35 | 36 | # settings management 37 | reset.title=Linkificator - \u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 38 | reset.message=\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e? 39 | 40 | export.title=Linkificator - \u042d\u043a\u0441\u043f\u043e\u0440\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a 41 | import.title=Linkificator - \u0418\u043c\u043f\u043e\u0440\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a 42 | -------------------------------------------------------------------------------- /legacy/options.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | &settings.separator-domains; 62 | &settings.separator-domains; 64 | 65 | 66 | 67 | 68 | 69 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /legacy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Linkificator", 3 | "name": "linkificator", 4 | "version": "2.3.2", 5 | "license": "MPL 2.0", 6 | "author": "MarkaPola", 7 | "id": "linkificator@markapola", 8 | "description": "Converts text links into clickable links...", 9 | "contributors": ["Icons from www.fasticon.com"], 10 | "keywords": [ 11 | "jetpack" 12 | ], 13 | "engines": { 14 | "firefox": ">=38.0a1" 15 | }, 16 | "permissions": { 17 | "private-browsing": true, 18 | "multiprocess": true 19 | }, 20 | "main": "lib/main.js" 21 | } 22 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // The main module of the Linkificator Add-on. 7 | // author: MarkaPola 8 | 9 | const webext = require("sdk/webextension"); 10 | 11 | function sendPreferences (port) { 12 | function splitProtocols (data) { 13 | function Element (data) { 14 | let parts1 = data.split('~'); 15 | if (parts1.length != 2) { 16 | return null; 17 | } 18 | 19 | let parts2 = parts1[1].split('#'); 20 | let count = 2; 21 | 22 | if (parts2.length == 2) { 23 | count = parseInt(parts2[1], 10); 24 | } 25 | 26 | let trailer = ":"; 27 | for (let index = 0; index < count; ++index) { 28 | trailer += "/"; 29 | } 30 | 31 | return { 32 | pattern: parts1[0]+trailer, 33 | term: parts2[0]+trailer 34 | }; 35 | } 36 | 37 | let result = []; 38 | for (let element of data.trim().split(';')) { 39 | let protocol = Element(element); 40 | if (protocol) 41 | result.push (protocol); 42 | } 43 | 44 | return result; 45 | } 46 | function splitSubdomains (data) { 47 | function Element (data) { 48 | let parts = data.split('~'); 49 | if (parts.length == 3) { 50 | return { 51 | filter: parts[0], 52 | pattern: parts[1], 53 | term: parts[2] 54 | }; 55 | } 56 | else { 57 | return null; 58 | } 59 | } 60 | 61 | let result = []; 62 | for (let element of data.trim().split(';')) { 63 | let subdomain = Element(element); 64 | if (subdomain) 65 | result.push (subdomain); 66 | } 67 | 68 | return result; 69 | } 70 | function splitExcludedElements (data) { 71 | let elements = data.trim().split(';'); 72 | 73 | for (let index = 0; index < elements.length; ++index) { 74 | let element = elements[index]; 75 | if (element.charAt(0) == '@') 76 | elements[index] = '*[' + element + ']'; 77 | } 78 | 79 | return elements; 80 | } 81 | function splitTopLevelDomains (data) { 82 | return data.trim().split(';').sort().filter((value, index, self) => self.indexOf(value) === index); 83 | } 84 | 85 | 86 | const prefs = require('sdk/simple-prefs').prefs; 87 | 88 | let settings = {}; 89 | 90 | // convert legacy preferences to webextension format 91 | settings.manual = prefs.manual; 92 | settings.contextMenuIntegration = prefs.contextMenuIntegration; 93 | 94 | settings.hotkeys = { 95 | toggle: prefs.hotkeyToggle, 96 | manual: prefs.hotkeyManual, 97 | manage: prefs.hotkeyManage, 98 | parse: prefs.hotkeyParse 99 | }; 100 | 101 | settings.domains = { 102 | useRegExp: prefs.useRegExp, 103 | type: prefs.filterMode, 104 | list: { 105 | white: prefs.whitelist.trim().split(/\s+/), 106 | black: prefs.blacklist.trim().split(/\s+/).filter(item => item !== '^about:') 107 | } 108 | }; 109 | 110 | settings.style = { 111 | text: { 112 | override: prefs.overrideTextColor, 113 | color: prefs.linkColor 114 | }, 115 | background: { 116 | override: prefs.overrideBackgroundColor, 117 | color: prefs.backgroundColor 118 | } 119 | }; 120 | 121 | settings.predefinedRules = { 122 | support: { 123 | email: { 124 | active: prefs.supportEmail, 125 | useTLD: prefs.emailUseTLD 126 | }, 127 | standard: { 128 | active: prefs.supportStandardURLs, 129 | useSubdomains: prefs.standardURLUseSubdomains, 130 | useTLD: prefs.standardURLUseTLD, 131 | linkifyAuthority: prefs.standardURLlinkifyAuthority 132 | } 133 | }, 134 | protocols: splitProtocols(prefs.protocols), 135 | subdomains: splitSubdomains(prefs.subdomains), 136 | excludedElements: splitExcludedElements(prefs.excludedElements) 137 | }; 138 | 139 | settings.tldGenerics = { 140 | active: prefs.useGTLDs, 141 | domains: splitTopLevelDomains(prefs.gTLDs) 142 | }; 143 | settings.tldCountryCodes = { 144 | active: prefs.useCcTLDs, 145 | domains: splitTopLevelDomains(prefs.ccTLDs) 146 | }; 147 | settings.tldGgeographics = { 148 | active: prefs.useGeoTLDs, 149 | domains: splitTopLevelDomains(prefs.geoTLDs) 150 | }; 151 | settings.tldCommunities = { 152 | active: prefs.useCommunityTLDs, 153 | domains: splitTopLevelDomains(prefs.communityTLDs) 154 | }; 155 | settings.tldBrands = { 156 | active: prefs.useBrandTLDs, 157 | domains: splitTopLevelDomains(prefs.brandTLDs) 158 | }; 159 | 160 | settings.customRules = { 161 | support: { 162 | before: prefs.supportCustomRulesBefore, 163 | after: prefs.supportCustomRulesAfter 164 | }, 165 | rules: JSON.parse(prefs.customRules) 166 | }; 167 | 168 | settings.extraFeatures = { 169 | support: { 170 | inlineElements: prefs.supportInlineElements, 171 | autoLinkification: prefs.automaticLinkification 172 | }, 173 | inlineElements: prefs.inlineElements.trim().split(";"), 174 | maxDataSize: prefs.maxDataSize, 175 | autoLinkification: { 176 | delay: prefs.autoLinkificationDelay, 177 | interval: { 178 | active: prefs.autoLinkificationInterval, 179 | value: prefs.autoLinkificationIntervalValue 180 | }, 181 | threshold: { 182 | active: prefs.autoLinkificationThreshold, 183 | value: prefs.autoLinkificationThresholdValue 184 | } 185 | } 186 | }; 187 | 188 | try { 189 | settings.processing = JSON.parse(prefs.processing); 190 | } catch (e) { 191 | // erroneous JSON, use default 192 | settings.processing = {interval: 10, iterations: 40}; 193 | } 194 | 195 | port.postMessage({ 196 | id: 'set-preferences', 197 | preferences: { 198 | config: { 199 | sync: prefs.sync, 200 | activated: prefs.activated 201 | }, 202 | settings: settings 203 | } 204 | }); 205 | } 206 | 207 | 208 | exports.main = function (options) { 209 | webext.startup().then(({browser}) => { 210 | browser.runtime.onConnect.addListener(port => { 211 | if (port.name === "legacy-channel") { 212 | // for new installation or upgrade, 213 | // show an informational page 214 | // forward current settings to new extension 215 | if (options.loadReason == 'install' || options.loadReason == 'upgrade') { 216 | port.postMessage({id: 'show-release-notes'}); 217 | 218 | sendPreferences(port); 219 | } 220 | } 221 | }); 222 | }); 223 | }; 224 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "title": "Linkificator", 4 | "name": "linkificator", 5 | "icon": "icon48.png", 6 | "version": "3.0.1", 7 | "license": "MPL 2.0", 8 | "author": "MarkaPola", 9 | "id": "linkificator@markapola", 10 | "description": "Converts text links into clickable links...", 11 | "contributors": ["Icons from www.fasticon.com (http://www.fasticon.com)", 12 | "Localization infrastructure: Crowdin.com (http://crowdin.com)"], 13 | "translators": ["MarkaPola", "Капелька Яда"], 14 | "engines": { 15 | "firefox": ">=55.0" 16 | }, 17 | "permissions": { 18 | "private-browsing": true, 19 | "multiprocess": true 20 | }, 21 | "hasEmbeddedWebExtension": true, 22 | "locales": { 23 | "en-US": { 24 | "description": "Converts text links into clickable links..." 25 | }, 26 | "fr": { 27 | "description": "Transforme les liens textuels en liens hypertexte cliquables..." 28 | }, 29 | "ru": { 30 | "description": "Преобразует текстовые ссылки в кликабельные." 31 | } 32 | }, 33 | "main": "./main.js" 34 | } 35 | -------------------------------------------------------------------------------- /webextension/content_scripts/statistics.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* Statistics management - Linkificator's module 7 | * author: MarkaPola */ 8 | 9 | 10 | function Statistics (document, action) { 11 | "use strict"; 12 | 13 | const countLabel = 'data-linkificator-count'; 14 | const timeLabel = 'data-linkificator-time'; 15 | 16 | var body = document.body; 17 | 18 | function getInt (value) { 19 | let v = parseInt(value, 10); 20 | return isNaN(v) ? 0 : v; 21 | } 22 | 23 | function getStats (count, time) { 24 | return {links: getInt(count), time: getInt(time)}; 25 | } 26 | 27 | if (action == 'undo') { 28 | if (body.hasAttribute(countLabel)) { 29 | body.removeAttribute(countLabel); 30 | body.removeAttribute(timeLabel); 31 | } 32 | return { 33 | get: function () { 34 | return getStats(0, 0); 35 | } 36 | }; 37 | } 38 | 39 | var elapse = 0; 40 | var startTime = Date.now(); 41 | 42 | if (action === 'parse') { 43 | body.setAttribute(countLabel, 0); 44 | body.setAttribute(timeLabel, 0); 45 | } else if (action == 're-parse') { 46 | elapse = getInt(body.getAttribute(timeLabel)); 47 | } 48 | 49 | return { 50 | store: function (count) { 51 | let links = getInt(body.getAttribute(countLabel)); 52 | 53 | body.setAttribute(countLabel, links+count); 54 | body.setAttribute(timeLabel, elapse+(Date.now()-startTime)); 55 | }, 56 | 57 | get: function () { 58 | if (body.hasAttribute(countLabel)) { 59 | return getStats(body.getAttribute(countLabel), 60 | body.getAttribute(timeLabel)); 61 | } else { 62 | return getStats(0, 0); 63 | } 64 | } 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /webextension/main.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // The main module of the Linkificator Add-on. 7 | // author: MarkaPola 8 | 9 | 10 | // Display release-notes on install or update 11 | function displayReleaseNotes () { 12 | function display(alarm) { 13 | if (alarm.name === 'linkificator-release-notes') { 14 | browser.alarms.clear('linkificator-release-notes'); 15 | browser.alarms.onAlarm.removeListener(display); 16 | browser.tabs.create({url: "/resources/doc/release-notes.html", active: true}); 17 | } 18 | } 19 | 20 | browser.alarms.onAlarm.addListener(display); 21 | browser.alarms.create('linkificator-release-notes', {when: Date.now()+2000}); 22 | } 23 | 24 | browser.runtime.onInstalled.addListener(details => { 25 | if ((details.reason === 'install' || details.reason === 'update')) { 26 | displayReleaseNotes(); 27 | } 28 | }); 29 | 30 | 31 | Configurator().then(config => { 32 | 33 | let {configurator, properties} = config; 34 | 35 | 36 | function Statistics () { 37 | this.links = 0; 38 | this.time = 0; 39 | } 40 | 41 | 42 | class Worker { 43 | constructor (port, tab) { 44 | this._port = port; 45 | this._tab = tab; 46 | this._contentType = null; 47 | this._statistics = new Statistics(); 48 | } 49 | 50 | get tab () { 51 | return this._tab; 52 | } 53 | 54 | get contentType () { 55 | return this._contentType; 56 | } 57 | set contentType (contentType) { 58 | return this._contentType = contentType; 59 | } 60 | 61 | get isValidDocument () { 62 | if (!this._contentType) 63 | return false; 64 | 65 | return this._contentType.startsWith('text/html') || this._contentType.startsWith('text/plain') 66 | || this._contentType.startsWith('application/xhtml'); 67 | } 68 | 69 | sendMessage (message) { 70 | this._port.postMessage(message); 71 | } 72 | 73 | get statistics () { 74 | return this._statistics; 75 | } 76 | set statistics (stats) { 77 | this._statistics = stats; 78 | } 79 | } 80 | 81 | class Workers extends Map { 82 | constructor () { 83 | super(); 84 | } 85 | 86 | *getTabs (query) { 87 | let set = new Set(); 88 | if (query === undefined) query = {validOnly: false}; 89 | 90 | for (const worker of this.values()) { 91 | if (!set.has(worker.tab)) { 92 | if (!query.validOnly || worker.isValidDocument) { 93 | set.add(worker.tab); 94 | yield worker.tab; 95 | } 96 | } 97 | } 98 | } 99 | 100 | *forTab (tab) { 101 | for (const worker of this.values()) { 102 | if (tab.id === worker.tab.id) 103 | yield worker; 104 | }; 105 | } 106 | 107 | isValidDocument (tab) { 108 | for (const worker of this.forTab(tab)) { 109 | if (worker.isValidDocument) { 110 | return true; 111 | } 112 | } 113 | 114 | return false; 115 | } 116 | isValidTab (tab) { 117 | return controler.linkifyURL(tab) && this.isValidDocument(tab); 118 | } 119 | 120 | getStatistics (tab) { 121 | let statistics = new Statistics(); 122 | 123 | for (const worker of this.forTab(tab)) { 124 | statistics.links += worker.statistics.links; 125 | statistics.time = Math.max(statistics.time, worker.statistics.time); 126 | }; 127 | 128 | return statistics; 129 | } 130 | } 131 | 132 | var workers = new Workers(); 133 | 134 | var controler = Controler(config); 135 | 136 | 137 | // TEMPORARY: Handle communication with legacy part of add-on 138 | let legacyChannel = browser.runtime.connect({name: 'legacy-channel'}); 139 | legacyChannel.onMessage.addListener(message => { 140 | switch(message.id) { 141 | case 'show-release-notes': 142 | displayReleaseNotes(); 143 | break; 144 | case 'set-preferences': 145 | configurator.updateProperties(message.preferences); 146 | break; 147 | } 148 | }); 149 | 150 | 151 | // handle tabs events 152 | browser.tabs.onCreated.addListener(tab => controler.setStatus({tab: tab})); 153 | 154 | browser.tabs.onActivated.addListener(info => { 155 | browser.tabs.get(info.tabId).then(tab => 156 | controler.contextMenu.update({enable: controler.isActive() && workers.isValidTab(tab)})); 157 | }); 158 | 159 | browser.tabs.onUpdated.addListener((tabId, info, tab) => { 160 | if (info.url) { 161 | let isValid = workers.isValidTab(tab); 162 | 163 | controler.setStatus({tab: tab, isValid: isValid}); 164 | 165 | // update context menu if this is one of the active tabs 166 | if (controler.isActive() && isValid) { 167 | controler.contextMenu.update({tabId: tab.id, enable: true}); 168 | } 169 | } 170 | }); 171 | 172 | // handle content_scripts 173 | browser.runtime.onConnect.addListener(port => { 174 | if (port.name !== 'linkificator' || port.sender.tab.id === browser.tabs.TAB_ID_NONE) { 175 | return; 176 | } 177 | 178 | workers.set(port, new Worker(port, {id: port.sender.tab.id, url: port.sender.tab.url})); 179 | 180 | port.onMessage.addListener(message => { 181 | let worker = workers.get(port); 182 | let tab = worker.tab; 183 | 184 | switch (message.id) { 185 | case 'content-type': 186 | worker.contentType = message.contentType; 187 | 188 | controler.setStatus({tab: tab, isValid: workers.isValidDocument(tab)}); 189 | 190 | if (controler.isActive() && controler.linkifyURL(tab) && worker.isValidDocument) { 191 | port.postMessage ({id: 'parse'}); 192 | } 193 | break; 194 | case 'configured': 195 | if (controler.isActive() && controler.isManual()) { 196 | // update context menu 197 | controler.contextMenu.update({tabId: tab.id, enable: true}); 198 | } 199 | break; 200 | case 'completed': 201 | if (controler.isActive() && !controler.isManual()) { 202 | // update context menu 203 | controler.contextMenu.update({tabId: tab.id, enable: true}); 204 | } 205 | break; 206 | case 'statistics': 207 | worker.statistics = message.statistics; 208 | 209 | if (controler.isActive() && controler.linkifyURL(tab)) { 210 | controler.setStatus({tab: tab, 211 | isValid: true, 212 | displayTooltip: true, 213 | displayBadge: properties.displayBadge, 214 | statistics: workers.getStatistics(tab)}); 215 | } 216 | break; 217 | case 'document-changed': 218 | if (controler.isActive() && worker.isValidDocument) { 219 | port.postMessage ({id: controler.linkifyURL(tab) ? 're-parse' : 'undo'}); 220 | } 221 | break; 222 | } 223 | }); 224 | 225 | port.onDisconnect.addListener(port => { 226 | workers.delete(port); 227 | }); 228 | }); 229 | 230 | 231 | // manage communication with popup 232 | browser.runtime.onMessage.addListener((message, sender, sendResponse) => { 233 | switch (message.id) { 234 | case 'tab-context': 235 | return browser.tabs.query({active: true, currentWindow: true}).then(tabs => { 236 | let context = {area: properties.area, 237 | activated: properties.activated, 238 | manual: properties.manual}; 239 | 240 | let tab = tabs[0]; 241 | context.tab = tab; 242 | context.status = controler.getStatus({tab: tab, 243 | isValid: workers.isValidDocument(tab)}); 244 | 245 | return context; 246 | }); 247 | break; 248 | default: 249 | return undefined; 250 | } 251 | }); 252 | 253 | 254 | // attach listeners to various events 255 | controler.onBadgeChanged.addListener(info => { 256 | if (controler.isActive()) { 257 | for (const tab of workers.getTabs({validOnly: true})) { 258 | // recompute links count for every tab 259 | if (controler.linkifyURL(tab)) { 260 | controler.setStatus({tab: tab, 261 | isValid: true, 262 | displayBadge: info.displayBadge, 263 | statistics: info.displayBadge ? workers.getStatistics(tab) 264 | : null}); 265 | } 266 | } 267 | } 268 | }); 269 | 270 | controler.onContextMenuChanged.addListener(info => { 271 | if (info.activated) { 272 | browser.tabs.query({active: true}).then(tabs => { 273 | for (const tab of tabs) { 274 | controler.contextMenu.update({enable: workers.isValidTab(tab)}); 275 | } 276 | }); 277 | } 278 | }); 279 | 280 | controler.onActivated.addListener(info => { 281 | // context menu configuration 282 | if (info.activated) { 283 | browser.tabs.query({active: true}).then(tabs => { 284 | for (const tab of tabs) { 285 | controler.contextMenu.update({enable: workers.isValidTab(tab)}); 286 | } 287 | }); 288 | } 289 | 290 | for (const tab of workers.getTabs()) { 291 | let isValid = workers.isValidTab(tab); 292 | 293 | controler.setStatus({tab: tab, 294 | isValid: isValid}); 295 | 296 | if (isValid && (!controler.isManual() || !info.activated)) { 297 | for (const worker of workers.forTab(tab)) { 298 | worker.sendMessage({id: info.activated ? 'parse' : 'undo'}); 299 | } 300 | } 301 | } 302 | }); 303 | 304 | controler.onUpdate.addListener(info => { 305 | switch(info.action) { 306 | case 'parse': 307 | case 're-parse': 308 | case 'undo': 309 | for (const worker of workers.forTab(info.tab)) { 310 | worker.sendMessage({id: info.action}); 311 | } 312 | break; 313 | } 314 | }); 315 | 316 | }).catch(reason => console.error(reason)); 317 | -------------------------------------------------------------------------------- /webextension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Linkificator", 4 | "version": "3.3.3", 5 | "description": "__MSG_extensionDescription__", 6 | "icons": { 7 | "16": "resources/icons/link16-on.png", 8 | "32": "resources/icons/link32-on.png", 9 | "48": "resources/icons/icon48.png", 10 | "64": "resources/icons/icon64.png", 11 | "128": "resources/icons/icon128.png" 12 | }, 13 | 14 | "developer": { 15 | "name": "MarkaPola", 16 | "url": "https://github.com/MarkaPola/Linkificator" 17 | }, 18 | 19 | "default_locale": "en_US", 20 | 21 | "applications": { 22 | "gecko": { 23 | "id": "linkificator@markapola", 24 | "strict_min_version": "60.0" 25 | } 26 | }, 27 | 28 | "permissions": [ 29 | "tabs", "contextMenus", "history", "storage", "alarms" 30 | ], 31 | 32 | "options_ui": { 33 | "browser_style": true, 34 | "page": "options/options.html" 35 | }, 36 | 37 | "browser_action": { 38 | "browser_style": true, 39 | "default_area": "navbar", 40 | "default_icon": { 41 | "16": "resources/icons/link16-on.png", 42 | "32": "resources/icons/link32-on.png" 43 | }, 44 | "default_title": "Linkificator", 45 | "default_popup": "popup/popup.html" 46 | }, 47 | 48 | "commands": { 49 | "Toggle": { 50 | "description": "__MSG_settings@shortcuts@toggle__ (Alt+Shift+O)" 51 | }, 52 | "Manual": { 53 | "description": "__MSG_settings@shortcuts@manual__ (Alt+Shift+M)" 54 | }, 55 | "Manage": { 56 | "description": "__MSG_settings@shortcuts@manage__ (Alt+Shift+X)" 57 | }, 58 | "Update": { 59 | "description": "__MSG_settings@shortcuts@update__ (Alt+Shift+U)" 60 | } 61 | }, 62 | 63 | "background": { 64 | "scripts" : [ 65 | "options/ShortcutCustomizeUI.js", 66 | "configurator.js", 67 | "controler.js", 68 | "main.js"] 69 | }, 70 | "content_scripts": [ 71 | { 72 | "matches": ["*://*/*", "file://*/*"], 73 | "all_frames": true, 74 | "run_at": "document_end", 75 | "js": [ 76 | "resources/js/thread.js", 77 | "resources/js/DOMutils.js", 78 | "content_scripts/statistics.js", 79 | "content_scripts/linkificator.js"] 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /webextension/options/advanced-options.css: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | * author: MarkaPola 7 | */ 8 | 9 | *, html { 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | * {box-sizing: border-box} 15 | 16 | body { 17 | font-family: "Arial"; 18 | background-color: #f9f9fa; 19 | } 20 | 21 | 22 | /* Style the tab */ 23 | div.tab { 24 | position: fixed; 25 | float: left; 26 | width: 230px; 27 | height: 100vh; 28 | padding-top: 70px; 29 | } 30 | 31 | /* Style the buttons inside the tab */ 32 | .tablinks { 33 | display: inline-flex; 34 | background-color: inherit; 35 | color: #0c0c0d; 36 | fill: #0c0c0d; 37 | margin-left: 34px; 38 | padding-left: 16px; 39 | padding-right: 16px; 40 | padding-top: 15px; 41 | padding-bottom: 15px; 42 | width: calc(100% - 34px); 43 | border: none; 44 | border-radius: 2px; 45 | outline: none; 46 | font-size: 1rem; 47 | transition: background-color 150ms; 48 | -moz-user-select: none; 49 | } 50 | 51 | .tablinks svg { 52 | margin-right: 10px; 53 | } 54 | 55 | /* Change background color of buttons on hover */ 56 | .tablinks:hover { 57 | background-color: rgba(12, 12, 13, 0.1); 58 | } 59 | .tablinks:active:hover { 60 | color: #0060df; 61 | fill: #0060df; 62 | background-color: rgba(12, 12, 13, 0.15); 63 | } 64 | 65 | /* Create an active/current "tab button" class */ 66 | .tablinks.active { 67 | color: #0a84ff; 68 | fill: #0a84ff; 69 | } 70 | .tablinks.active:hover { 71 | color: #0a84ff; 72 | fill: #0a84ff; 73 | background-color: rgba(12, 12, 13, 0.20); 74 | } 75 | .tablinks.active:active:hover { 76 | color: #0060df; 77 | fill: #0060df; 78 | } 79 | 80 | /* Style the tab content */ 81 | .tabcontent { 82 | margin-left: 230px; 83 | padding: 30px 12px; 84 | width: calc(100% - 230px); 85 | border-left: none; 86 | height: 100vh; 87 | } 88 | 89 | .tabcontent > fieldset { 90 | border-top: 2px solid #c1c1c1; 91 | border-bottom: none; 92 | border-left: none; 93 | border-right: none; 94 | padding-left: 2em; 95 | padding-top: 10px; 96 | padding-bottom: 25px; 97 | float:left; 98 | width: 95%; 99 | } 100 | 101 | legend { 102 | padding-left: 10px; 103 | padding-right: 10px; 104 | font-size: 1.2rem; 105 | } 106 | 107 | 108 | .settings-entry > input[type="checkbox"] { 109 | margin-top: 3px; 110 | } 111 | .settings-entry > input[type="text"] { 112 | width: calc(50% - 400px); 113 | } 114 | .settings-entry > input.long-text { 115 | width: calc(95% - 400px); 116 | } 117 | 118 | .settings-entry label { 119 | width: 400px; 120 | } 121 | 122 | .settings-entry2 { 123 | margin-top: 1em; 124 | margin-bottom: 1em; 125 | display: flex; 126 | flex-direction: row; 127 | } 128 | .settings-entry2 label { 129 | width: 200px; 130 | } 131 | .settings-entry2 > input[type="text"] { 132 | flex-grow: 1; 133 | height: 2em; 134 | } 135 | .settings-entry2 > textarea { 136 | margin-left: 6px; 137 | flex-grow: 1; 138 | height: 6em; 139 | resize: vertical; 140 | } 141 | .settings-entry2 .flex-space { 142 | flex-grow: 1; 143 | } 144 | .settings-entry2 > input[type="number"] { 145 | height: 2em; 146 | } 147 | 148 | .indent label { 149 | margin-left: 25px; 150 | margin-right: 4px; 151 | } 152 | 153 | .secondary-input { 154 | margin-left: 20px; 155 | width: 4em; 156 | } 157 | 158 | .secondary-label { 159 | margin-left: 6px !important; 160 | width: 50px !important; 161 | } 162 | 163 | .tld-checkbox { 164 | height: 1em; 165 | } 166 | 167 | .reset-button { 168 | float: right; 169 | height: 2em; 170 | margin-left: 30px; 171 | margin-right: 20px; 172 | } 173 | 174 | #max-data-size { 175 | width: 8em; 176 | } 177 | 178 | 179 | /* 180 | * Custom Rules Tab 181 | */ 182 | 183 | /*** Rule editor : Modal window ***/ 184 | .modal { 185 | display: none; /* Hidden by default */ 186 | position: fixed; /* Stay in place */ 187 | z-index: 999; /* Sit on top */ 188 | padding-top: 20%; /* Location of the box */ 189 | left: 0; 190 | top: 0; 191 | width: 100%; /* Full width */ 192 | height: 100%; /* Full height */ 193 | overflow: auto; /* Enable scroll if needed */ 194 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ 195 | } 196 | .modal-content { 197 | display: block; 198 | background-color: #fefefe; 199 | margin: auto; 200 | padding: 20px; 201 | border-style: solid; 202 | border-width: 1px; 203 | border-radius: 6px; 204 | border-color: #888; 205 | width: 700px; 206 | overflow: hidden; 207 | } 208 | 209 | .rule-entry label { 210 | width: 80px !important; 211 | } 212 | .rule-entry > input[type="text"] { 213 | width: calc(100% - 140px) !important; 214 | } 215 | 216 | #rule-editor\.error { 217 | width: 70%; 218 | height: 1em; 219 | margin-left: 9px; 220 | font-weight: bold; 221 | color: red; 222 | } 223 | 224 | .rule-editor\.buttons { 225 | float: right; 226 | margin-right: 22px; 227 | } 228 | 229 | .rule-editor\.buttons button { 230 | margin: 8px; 231 | } 232 | 233 | .modal-content button { 234 | vertical-align: top; 235 | font-size: 0.95em; 236 | } 237 | 238 | /*** special configuration for tabcontent ***/ 239 | #custom-rules.tabcontent > fieldset { 240 | height: 100%; 241 | } 242 | 243 | /*** Rules List ***/ 244 | .rules-container { 245 | margin-top: 0.5em; 246 | margin-left: 10%; 247 | 248 | width: 80%; 249 | height: 70%; 250 | } 251 | 252 | .rules-container > .rules-commands { 253 | margin-bottom: 20px; 254 | } 255 | 256 | div.rules-commands button { 257 | float: right; 258 | } 259 | 260 | .rules-container > .rules-list { 261 | height: 100%; 262 | } 263 | 264 | .rules-list { 265 | border-style: solid; 266 | border-width: 1px; 267 | border-radius: 2px; 268 | border-color: #c1c1c1; 269 | 270 | overflow: auto; 271 | 272 | display: flex; 273 | flex-direction: column; 274 | } 275 | 276 | .overflow { 277 | flex-grow: 1; 278 | } 279 | 280 | .rules-list > table { 281 | width: 100%; 282 | } 283 | 284 | .rules-table { 285 | table-layout: fixed; 286 | border-collapse: collapse; 287 | } 288 | 289 | .rules-table tr, .rules-table td, .rules-table div { 290 | display: inline-block; 291 | width: 100%; 292 | } 293 | .rules-table tr:hover { 294 | background-color: rgba(0, 149, 221, 0.25); 295 | } 296 | 297 | .settings-rule { 298 | width: calc(100% - 10px) !important; 299 | margin-top: 4px; 300 | margin-bottom: 4px; 301 | margin-left: 5px; 302 | -moz-user-select: none; 303 | cursor: move; 304 | border-width: 2px; 305 | border-style: dashed; 306 | border-color: rgba(0,0,0,0.0); 307 | } 308 | /* Drag n drop customization */ 309 | .settings-rule.dragover { 310 | border-color: #000; 311 | } 312 | 313 | .rule-name { 314 | margin-left: 10px; 315 | width: calc(100% - 80px) !important; 316 | } 317 | 318 | .settings-rule input[type="checkbox"] { 319 | float: left; 320 | margin-top: 3px; 321 | } 322 | 323 | .settings-rule input[type="image"] { 324 | border-style: none; 325 | background-color: rgba(0,0,0,0.0); 326 | background-repeat: no-repeat; 327 | background-position: 50% 50%; 328 | width: 16px; 329 | height: 16px; 330 | margin-top: 3px; 331 | margin-right: 5px; 332 | } 333 | 334 | .rule\.buttons { 335 | float: right; 336 | width: auto !important; 337 | display: inline-block; 338 | } 339 | 340 | .edit-button { 341 | background-image: url(edit.png); 342 | } 343 | .edit-button:hover, 344 | .edit-button:active, 345 | .edit-button:focus { 346 | background-image: url(edit-hover.png); 347 | } 348 | 349 | .delete-button { 350 | background-image: url(delete.png); 351 | } 352 | .delete-button:hover, 353 | .delete-button:active, 354 | .delete-button:focus { 355 | background-image: url(delete-hover.png); 356 | } 357 | 358 | -------------------------------------------------------------------------------- /webextension/options/delete-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/options/delete-hover.png -------------------------------------------------------------------------------- /webextension/options/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/options/delete.png -------------------------------------------------------------------------------- /webextension/options/edit-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/options/edit-hover.png -------------------------------------------------------------------------------- /webextension/options/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/options/edit.png -------------------------------------------------------------------------------- /webextension/options/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/options/empty.png -------------------------------------------------------------------------------- /webextension/options/options.css: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | * author: MarkaPola 7 | */ 8 | 9 | form { 10 | color: #0c0c0d; 11 | background-color: #f9f9fa; 12 | } 13 | 14 | .settings-entry > input[type="text"] { 15 | width: calc(50% - 200px); 16 | } 17 | .settings-entry > input.long-text { 18 | width: calc(95% - 200px); 19 | } 20 | .settings-entry input[type="color"] { 21 | margin-left: 15px; 22 | } 23 | 24 | .settings-entry label { 25 | width: 200px; 26 | } 27 | 28 | form > .settings-entry { 29 | width: 100%; 30 | } 31 | 32 | form > hr { 33 | background-color: #c1c1c1; 34 | height: 1px; 35 | border: none; 36 | width: 100%; 37 | } 38 | 39 | input[type="image"] { 40 | height: 25px; 41 | } 42 | 43 | .key-combination input, .key-combination button { 44 | vertical-align: top; 45 | } 46 | -------------------------------------------------------------------------------- /webextension/options/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 48 |
49 |
50 | 51 | 53 |
54 |
55 |
56 | 57 | 58 | 59 |
60 |
61 | 62 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 | 73 | 74 |
75 |
76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /webextension/options/options.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // author: MarkaPola 7 | 8 | // 9 | // Manage the options page of the add-on 10 | // 11 | 12 | 13 | //=============== settings management ===================== 14 | var properties = {}; 15 | 16 | function updatePreference (id, value) { 17 | function setCheckbox (id, checked) { 18 | $(id).checked = checked; 19 | } 20 | function setColorSelector (type, config) { 21 | setCheckbox(`override-${type}-color`, config.override); 22 | let colorPicker = $(`href-${type}-color`); 23 | colorPicker.value = config.color; 24 | colorPicker.disabled = !config.override; 25 | } 26 | 27 | properties[id] = value; 28 | 29 | switch (id) { 30 | // basic settings 31 | case 'activated': 32 | setCheckbox('activated', properties.activated); 33 | break; 34 | case 'manual': 35 | setCheckbox('on-demand', properties.manual); 36 | break; 37 | case 'displayBadge': 38 | setCheckbox('display-counter', properties.displayBadge); 39 | break; 40 | case 'contextMenuIntegration': 41 | setCheckbox('context-menu', properties.contextMenuIntegration); 42 | break; 43 | // domains management 44 | case 'domains': 45 | setCheckbox('use-regexp', properties.domains.useRegExp); 46 | $('domain-filtering-mode').value = properties.domains.type; 47 | let domainList = $('domains-list'); 48 | if (properties.domains.type == 'none') { 49 | domainList.disabled = true; 50 | domainList.value = ''; 51 | } else { 52 | domainList.disabled = false; 53 | domainList.value = properties.domains.list[properties.domains.type].join(' '); 54 | } 55 | break; 56 | // link colors management 57 | case 'style': 58 | setColorSelector('text', properties.style.text); 59 | setColorSelector('background', properties.style.background); 60 | break; 61 | case 'sync': 62 | // settings management 63 | setCheckbox('sync-settings', properties.sync); 64 | 65 | } 66 | } 67 | 68 | function initializePreferences () { 69 | return browser.storage.local.get({sync: false, activated: true}).then(result => { 70 | properties.area = result.sync ? 'sync' : 'local'; 71 | updatePreference('sync', result.sync); 72 | updatePreference('activated', result.activated); 73 | 74 | ShortcutCustomizeUI.build().then(list => { 75 | // remove any current shortcuts list 76 | let shortcuts = $('shortcuts'); 77 | shortcuts.parentNode.replaceChild(shortcuts.cloneNode(false), shortcuts); 78 | // insert new list 79 | $('shortcuts').appendChild(list); 80 | list.addEventListener('ShortcutChanged', event => { 81 | properties.hotKeys[event.detail.name] = event.detail.key; 82 | browser.storage[properties.area].set({hotKeys: properties.hotKeys}).catch(reason => console.error(reason)); 83 | }); 84 | }); 85 | 86 | return browser.storage[properties.area].get().then(result => { 87 | for (let id in result) 88 | updatePreference(id, result[id]); 89 | 90 | return true; 91 | }); 92 | }); 93 | } 94 | 95 | function managePreferences () { 96 | function addCheckboxManager (id, preference, area = properties.area) { 97 | let checkbox = $(id); 98 | checkbox.addEventListener('change', event => { 99 | properties[preference] = checkbox.checked; 100 | browser.storage[area].set({[preference]: properties[preference]}).catch(reason => console.error(reason)); 101 | }); 102 | } 103 | function addColorManager (type, area = properties.area) { 104 | // checkbox 105 | let checkbox = $(`override-${type}-color`); 106 | let colorPicker = $(`href-${type}-color`); 107 | 108 | checkbox.addEventListener('change', event => { 109 | properties.style[type].override = checkbox.checked; 110 | colorPicker.disabled = !checkbox.checked; 111 | 112 | browser.storage[properties.area].set({style: properties.style}).catch(reason => console.error(reason)); 113 | }); 114 | colorPicker.addEventListener('change', event => { 115 | properties.style[type].color = colorPicker.value; 116 | 117 | browser.storage[properties.area].set({style: properties.style}).catch(reason => console.error(reason)); 118 | }); 119 | } 120 | 121 | addCheckboxManager('activated', 'activated', 'local'); 122 | addCheckboxManager('on-demand', 'manual'); 123 | addCheckboxManager('display-counter', 'displayBadge'); 124 | addCheckboxManager('context-menu', 'contextMenuIntegration'); 125 | 126 | // domain management 127 | let useRegex = $('use-regexp'); 128 | let select = $('domain-filtering-mode'); 129 | let domainList = $('domains-list'); 130 | useRegex.addEventListener('change', event => { 131 | properties.domains.useRegExp = useRegex.checked; 132 | 133 | browser.storage[properties.area].set({domains: properties.domains}).catch(reason => console.error(reason)); 134 | }); 135 | select.addEventListener('change', event => { 136 | properties.domains.type = select.value; 137 | if (select.value === 'none') { 138 | domainList.disabled = true; 139 | domainList.value = ''; 140 | } else { 141 | domainList.disabled = false; 142 | domainList.value = properties.domains.list[select.value].join(' '); 143 | } 144 | browser.storage[properties.area].set({domains: properties.domains}).catch(reason => console.error(reason)); 145 | }); 146 | domainList.addEventListener('change', event => { 147 | properties.domains.list[properties.domains.type] = domainList.value.split(' ').filter((value, index, self) => self.indexOf(value) === index); 148 | 149 | browser.storage[properties.area].set({domains: properties.domains}).catch(reason => console.error(reason)); 150 | }); 151 | 152 | // link colors management 153 | addColorManager('text'); 154 | addColorManager('background'); 155 | 156 | // advanced settings 157 | // check if Advanced options tab is already opened 158 | function advancedOptionsOpened (url) { 159 | return browser.runtime.getBrowserInfo().then(info => { 160 | if (parseInt(info.version) < 56) { 161 | // moz-extension: schema not accepted 162 | return browser.tabs.query({}).then(tabs => tabs.find(tab => tab.url === url)); 163 | } else { 164 | return browser.tabs.query({url: url}).then(tabs => tabs.length > 0 ? tabs[0] : undefined); 165 | } 166 | }); 167 | } 168 | $('advanced-settings').addEventListener('click', event => { 169 | const url = browser.extension.getURL('/options/advanced-options.html'); 170 | 171 | advancedOptionsOpened(url).then(tab => { 172 | if (tab) { 173 | return browser.tabs.update(tab.id, {active: true}).then(() => { 174 | return browser.history.deleteUrl({url: url}); 175 | }); 176 | } else { 177 | // create advanced settings tabs next to the options tab 178 | return browser.tabs.getCurrent().then(tab => { 179 | return browser.tabs.create({active: true, 180 | index: tab.index+1, 181 | url: url}).then(() => { 182 | // don't keep this url in history 183 | return browser.history.deleteUrl({url: url}); 184 | }); 185 | }); 186 | } 187 | }).catch(reason => console.error(reason)); 188 | }); 189 | 190 | // settings management 191 | let syncSettings = $('sync-settings'); 192 | syncSettings.addEventListener('change', event => { 193 | browser.storage.local.set({sync: syncSettings.checked}).then(() => { 194 | initializePreferences(); 195 | }).catch(reason => console.error(reason)); 196 | }); 197 | let resetDefault = $('reset-defaults'); 198 | resetDefault.addEventListener('click', event => { 199 | browser.runtime.sendMessage({id: 'reset-defaults'}).then(message => { 200 | if (message.done) initializePreferences(); 201 | }).catch(reason => console.error(reason)); 202 | }); 203 | } 204 | 205 | // audit storage changes 206 | browser.storage.onChanged.addListener((changes, area) => { 207 | if (area === 'local') { 208 | if (changes.hasOwnProperty('sync')) 209 | updatePreference('sync', changes.sync.newValue); 210 | if (changes.hasOwnProperty('activated')) 211 | updatePreference('activated', changes.activated.newValue); 212 | } 213 | 214 | if (area === properties.area) { 215 | for (let key in changes) { 216 | updatePreference(key, changes[key].newValue); 217 | } 218 | } 219 | }); 220 | 221 | 222 | document.addEventListener("DOMContentLoaded", 223 | () => { 224 | // UI tweak for windows 225 | browser.runtime.getPlatformInfo().then(platformInfo => { 226 | if (platformInfo.os === 'win') { 227 | $('linkificator-settings').style['font-family'] = 'Segoe UI'; 228 | $('linkificator-settings').style['font-size'] = '1.25rem'; 229 | } else if (platformInfo.os === 'mac') { 230 | $('linkificator-settings').style['font-family'] = 'Arial'; 231 | $('linkificator-settings').style['font-size'] = '1.1rem'; 232 | } 233 | }); 234 | initializePreferences().then(() => { 235 | managePreferences(); 236 | }); 237 | }, 238 | { 239 | capture: true, 240 | passive: true, 241 | once: true 242 | }); 243 | -------------------------------------------------------------------------------- /webextension/options/restart.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webextension/popup/extension.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /webextension/popup/link-exclude.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webextension/popup/link-include.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webextension/popup/link-update.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webextension/popup/popup.css: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | * author: MarkaPola 7 | */ 8 | 9 | .popup-content { 10 | display: flex; 11 | flex-direction: column; 12 | background-color: #fbfbfb; 13 | box-shadow: 4px 4px 2px 0px rgba(0,0,0,0.3); 14 | z-index: 1; 15 | } 16 | 17 | .popup-content > .popup-entry { 18 | color: #333; 19 | padding: 4px 10px; 20 | text-decoration: none; 21 | display: inline-block; 22 | width: calc(100%); 23 | margin-left: auto; 24 | margin-right: auto; 25 | } 26 | 27 | /* Change color of popup entries on hover */ 28 | .popup-entry:hover { 29 | background-color: rgba(0, 149, 221, 0.25); 30 | } 31 | 32 | .popup-content .entry-label { 33 | vertical-align: top; 34 | text-align: left; 35 | min-width: 90px; 36 | margin-left: 10px; 37 | width: auto; 38 | float: left; 39 | } 40 | 41 | .popup-content .entry-shortcut { 42 | color: #808080; 43 | text-align: right; 44 | margin-left: 8px; 45 | float: right; 46 | } 47 | 48 | .popup-content > hr { 49 | width: 100%; 50 | } 51 | .popup-content hr { 52 | color: #fff; 53 | float: right; 54 | margin-top: 0em; 55 | margin-bottom: 0.30em; 56 | } 57 | 58 | .popup-content input { 59 | vertical-align: top; 60 | font-size: 1.25rem; 61 | float: left; 62 | } 63 | .popup-content input[type="image"] { 64 | margin-right: 6px; 65 | width: 16px; 66 | height: 16px; 67 | } 68 | 69 | /* to manage disabled menu entries */ 70 | .popup-entry-disabled { 71 | color: #808080 !important; 72 | } 73 | .popup-entry-disabled:hover { 74 | background-color: #e7e7e7 !important; 75 | } 76 | 77 | .popup-entry-disabled input[type="image"], .popup-entry-disabled .entry-shortcut { 78 | visibility: hidden; 79 | } 80 | -------------------------------------------------------------------------------- /webextension/popup/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /webextension/popup/popup.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // author: MarkaPola 7 | 8 | // 9 | // Manage the options page of the add-on 10 | // 11 | 12 | Promise.prototype.finally = function (cb) { 13 | return this.then(v => Promise.resolve(cb(v)), 14 | v => Promise.reject(cb(v))); 15 | }; 16 | 17 | function managePopup (context) { 18 | $('panel-options').addEventListener('click', event => { 19 | browser.runtime.openOptionsPage().finally(() => window.close()); 20 | }); 21 | 22 | let manual = $('panel-manual'); 23 | manual.checked = context.manual; 24 | manual.addEventListener('click', event => { 25 | browser.storage[context.area].set({manual: manual.checked}).finally(() => window.close()); 26 | }); 27 | 28 | let activate = $('panel-activate'); 29 | let deactivate = $('panel-deactivate'); 30 | if (context.activated) { 31 | $('entry-activate').setAttribute('style', 'display: none'); 32 | $('panel-deactivate').addEventListener('click', event => { 33 | browser.storage.local.set({activated: false}).finally(() => window.close()); 34 | }); 35 | } else { 36 | $('entry-deactivate').setAttribute('style', 'display: none'); 37 | $('panel-activate').addEventListener('click', event => { 38 | browser.storage.local.set({activated: true}).finally(() => window.close()); 39 | }); 40 | } 41 | 42 | let entry, manage; 43 | if (context.status === 'excluded' || context.status === 'filtered') { 44 | $('entry-exclude').setAttribute('style', 'display: none'); 45 | entry = $('entry-include'); 46 | manage = $('panel-include'); 47 | } else { 48 | $('entry-include').setAttribute('style', 'display: none'); 49 | entry = $('entry-exclude'); 50 | manage = $('panel-exclude'); 51 | } 52 | if (context.status === 'not_processed') { 53 | entry.classList.add('popup-entry-disabled'); 54 | } 55 | manage.addEventListener('click', event => { 56 | browser.runtime.sendMessage({id: 'manage-url', info: context}).catch(reason => console.error(reason)).finally(() => window.close()); 57 | }); 58 | 59 | let linkify = $('panel-linkify'); 60 | if (context.status === 'processed') { 61 | linkify.addEventListener('click', event => { 62 | browser.runtime.sendMessage({id: 're-parse', info: context}).catch(reason => console.error(reason)).finally(() => window.close()); 63 | }); 64 | } else { 65 | $('entry-linkify').classList.add('popup-entry-disabled'); 66 | } 67 | 68 | // fill keyboard shortcuts 69 | browser.commands.getAll().then(commands => { 70 | for (let command of commands) { 71 | switch(command.name) { 72 | case 'Toggle': 73 | $('shortcut-activate').textContent = command.shortcut; 74 | $('shortcut-deactivate').textContent = command.shortcut; 75 | break; 76 | case 'Manual': 77 | $('shortcut-manual').textContent = command.shortcut; 78 | break; 79 | case 'Manage': 80 | $('shortcut-include').textContent = command.shortcut; 81 | $('shortcut-exclude').textContent = command.shortcut; 82 | break; 83 | case 'Update': 84 | $('shortcut-update').textContent = command.shortcut; 85 | break; 86 | } 87 | } 88 | }); 89 | } 90 | 91 | 92 | document.addEventListener("DOMContentLoaded", 93 | () => { 94 | // query current tab status 95 | browser.runtime.sendMessage({id: 'tab-context'}).then(context => { 96 | managePopup(context); 97 | }).catch(reason => console.error(reason)); 98 | }, 99 | { 100 | capture: true, 101 | passive: true, 102 | once: true 103 | }); 104 | -------------------------------------------------------------------------------- /webextension/resources/css/common.css: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | 6 | * author: MarkaPola 7 | */ 8 | 9 | /* 10 | * Utilitu classes 11 | */ 12 | .hidden { 13 | display: none !important; 14 | } 15 | 16 | 17 | /* 18 | * Global configuration for some elements 19 | */ 20 | input, select, option, button { 21 | color: #333; 22 | font-size: inherit; 23 | } 24 | input, button { 25 | border-style: solid; 26 | border-width: 1px; 27 | border-radius: 2px; 28 | border-color: #c1c1c1; 29 | } 30 | 31 | input:focus { 32 | border-color: rgba(0, 149, 221, 0.8); 33 | } 34 | input[type="text"] { 35 | text-align: left; 36 | } 37 | input[type="number"] { 38 | text-align: right; 39 | } 40 | input[type="image"] { 41 | padding: 4px; 42 | background-color: white; 43 | cursor: pointer; 44 | } 45 | input[type="image"]:hover { 46 | background-color: #f1f1f1; 47 | } 48 | input[type="image"]:active { 49 | background-color: #dadada; 50 | } 51 | input[type="image"]:disabled { 52 | background-color: #f1f1f1; 53 | color: #666666; 54 | cursor: inherit; 55 | } 56 | input[type="image"]::-moz-focus-inner { 57 | border: 0; 58 | } 59 | select { 60 | border-style: solid; 61 | border-width: 1px; 62 | border-color: #c1c1c1; 63 | border-radius: 2px; 64 | } 65 | select:hover { 66 | background-color: #f1f1f1; 67 | } 68 | select option:hover { 69 | box-shadow: 0 0 10px 100px rgba(0, 149, 221, 0.25) inset; 70 | } 71 | select option:checked { 72 | box-shadow: 0 0 10px 100px rgb(0, 149, 221) inset; 73 | } 74 | button { 75 | padding: 6px; 76 | background-color: white; 77 | cursor: pointer; 78 | } 79 | button:hover { 80 | background-color: #f1f1f1; 81 | } 82 | button:active { 83 | background-color: #dadada; 84 | } 85 | button:disabled { 86 | background-color: #f1f1f1; 87 | color: #666666; 88 | cursor: inherit; 89 | } 90 | button::-moz-focus-inner { 91 | border: 0; 92 | } 93 | 94 | 95 | .settings-entry { 96 | display: block; 97 | margin-top: 0.5em; 98 | margin-bottom: 0.5em; 99 | margin-left: 0; 100 | margin-right: 0; 101 | } 102 | 103 | .settings-entry input, .settings-entry select, .settings-entry button { 104 | vertical-align: top; 105 | } 106 | 107 | .settings-entry label { 108 | display: inline-block; 109 | margin-left: 9px; 110 | margin-right: 20px; 111 | vertical-align: top; 112 | text-align: left; 113 | } 114 | -------------------------------------------------------------------------------- /webextension/resources/doc/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/doc/favicon.png -------------------------------------------------------------------------------- /webextension/resources/doc/release-notes.css: -------------------------------------------------------------------------------- 1 | 2 | .large-text { 3 | font-size: 120%; 4 | } 5 | 6 | .version-changes { 7 | position: relative; 8 | left: 5%; 9 | max-width: 90%; 10 | } 11 | 12 | .feature-list-title { 13 | font-size: 115%; 14 | font-weight: bold; 15 | color: rgb(30, 100, 255); 16 | } 17 | 18 | html { 19 | background: linear-gradient(to bottom, rgb(220, 245, 255), rgb(255, 255, 255)); 20 | background-size: cover; 21 | background-repeat: no-repeat; 22 | } 23 | 24 | #linkificator-icon { 25 | vertical-align: middle; 26 | float: left; 27 | margin-top: 10pt; 28 | margin-right: 30pt; 29 | margin-bottom: 10pt; 30 | margin-left: 10pt; 31 | } 32 | -------------------------------------------------------------------------------- /webextension/resources/doc/release-notes.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | 7 | /* Release notes localization helper - Linkificator's module 8 | * author: MarkaPola */ 9 | 10 | 11 | function translateElements() { 12 | const children = document.querySelectorAll('*[data-l10n-id]'); 13 | for(let child of children) { 14 | if(!child.dataset.l10nNocontent) { 15 | const data = browser.i18n.getMessage(child.dataset.l10nId); 16 | if(data && data.length != 0) { 17 | let fragment = document.createRange().createContextualFragment(data); 18 | child.insertBefore(fragment, child.firstChild); 19 | } 20 | } 21 | } 22 | } 23 | 24 | 25 | 26 | document.addEventListener("DOMContentLoaded", 27 | () => translateElements(), 28 | { 29 | capture: true, 30 | passive: true, 31 | once: true 32 | }); 33 | -------------------------------------------------------------------------------- /webextension/resources/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/icon128.png -------------------------------------------------------------------------------- /webextension/resources/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/icon48.png -------------------------------------------------------------------------------- /webextension/resources/icons/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/icon64.png -------------------------------------------------------------------------------- /webextension/resources/icons/link16-excluded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link16-excluded.png -------------------------------------------------------------------------------- /webextension/resources/icons/link16-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link16-manual.png -------------------------------------------------------------------------------- /webextension/resources/icons/link16-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link16-off.png -------------------------------------------------------------------------------- /webextension/resources/icons/link16-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link16-on.png -------------------------------------------------------------------------------- /webextension/resources/icons/link32-excluded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link32-excluded.png -------------------------------------------------------------------------------- /webextension/resources/icons/link32-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link32-manual.png -------------------------------------------------------------------------------- /webextension/resources/icons/link32-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link32-off.png -------------------------------------------------------------------------------- /webextension/resources/icons/link32-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkaPola/Linkificator/c3a3f3347c4e5742e9102792c69e94cd3bf9e589/webextension/resources/icons/link32-on.png -------------------------------------------------------------------------------- /webextension/resources/js/DOMutils.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | // author: MarkaPola 7 | 8 | // 9 | // DOM Utilities 10 | // 11 | 12 | function $ (id) { 13 | return document.getElementById(id); 14 | } 15 | 16 | 17 | /* 18 | * Retrieve MIME type from DOM Document 19 | * 20 | * @param [object] document: DOM Document object 21 | */ 22 | 23 | function Document (doc) { 24 | "use strict"; 25 | 26 | var document = doc; 27 | 28 | return { 29 | get contentType () { 30 | let contentType = null; 31 | 32 | if (document) { 33 | contentType = document.contentType; 34 | 35 | if (!contentType) { 36 | // try to get content type for head section 37 | let ct = document.querySelector('meta[http-equiv="content-type" i]'); 38 | if (ct) { 39 | let content = ct.getAttribute('content'); 40 | if (content) { 41 | contentType = content.trim().split(/;|\s/)[0]; 42 | } 43 | } 44 | } 45 | 46 | if (! contentType) { 47 | // Check possible plain text page 48 | if (document.querySelector('link[href="resource://gre-resources/plaintext.css"]')) { 49 | contentType = 'text/plain'; 50 | } 51 | } 52 | } 53 | 54 | if (! contentType) { 55 | contentType = 'text/html'; 56 | } 57 | 58 | return contentType; 59 | } 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /webextension/resources/js/localization.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* Localization helper - Linkificator's module 7 | * author: MarkaPola */ 8 | 9 | function translateElementAttributes(element) { 10 | const attributes = [ 'title', 'accesskey', 'alt', 'label', 'placeholder', 'abbr', 'content', 'download', 'srcdoc', 'value' ]; 11 | const separator = '@'; 12 | 13 | const presentAttributes = element.dataset.l10nAttrs.split(","); 14 | 15 | // Translate allowed attributes. 16 | for(let attribute of presentAttributes) { 17 | let data; 18 | if(attributes.includes(attribute)) { 19 | data = browser.i18n.getMessage(element.dataset.l10nId + separator + attribute); 20 | } 21 | 22 | if(data && data.length != 0) { 23 | element.setAttribute(attribute, data); 24 | } 25 | } 26 | } 27 | 28 | function translateElements() { 29 | const children = document.querySelectorAll('*[data-l10n-id]'); 30 | for(let child of children) { 31 | if(!child.dataset.l10nNocontent) { 32 | const data = browser.i18n.getMessage(child.dataset.l10nId); 33 | if(data && data.length != 0) { 34 | child.textContent = data; 35 | } 36 | } 37 | 38 | if(child.dataset.l10nAttrs) { 39 | translateElementAttributes(child); 40 | } 41 | } 42 | } 43 | 44 | 45 | 46 | document.addEventListener("DOMContentLoaded", 47 | () => translateElements(), 48 | { 49 | capture: true, 50 | passive: true, 51 | once: true 52 | }); 53 | -------------------------------------------------------------------------------- /webextension/resources/js/thread.js: -------------------------------------------------------------------------------- 1 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* Thread helper - Linkificator's module 7 | * author: MarkaPola */ 8 | 9 | // javascript does not support parallel executions 10 | // to avoid any CPU total comsumption and UI freezes, this utility help execute tasks in the background 11 | 12 | // arguments to Thread function are: 13 | // action: object which MUST expose, at least, the following functions: 14 | // * execute: will execute a part of the work and must return true if work is completed 15 | // * finish: will execute all remaining tasks to complete the work 16 | // * abort: thread was aborted, remaining work must not be done 17 | // * complete: this function will be called when all work is done 18 | // interval: waiting time (in ms) between each execution. If not specified, 10ms will be used. 19 | // all functions are argumentless except abort and complete which pass thread reference. 20 | // 21 | // Thread functions are: 22 | // start: launch execution of action object in the background. 23 | // terminate: request to complete action in the foreground. 24 | // kill: stop background execution. action will not terminate. 25 | // 26 | // Here is a small example: each part of the work will be done at 100ms interval. 27 | // 28 | // var test = { 29 | // index: 0, 30 | // total: 100, 31 | // 32 | // execute: function () { 33 | // var part = Math.min((this.index + 3), this.total); 34 | // while (this.index < part) { 35 | // console.log (this.index); 36 | // this.index++; 37 | // } 38 | // console.log ("chunk done"); 39 | // 40 | // return this.index == this.total; 41 | // }, 42 | // finish: function () { 43 | // while (this.index++ < this.total) 44 | // console.log (this.index); 45 | // console.log ("complete done"); 46 | // }, 47 | // abort: function (thread) { 48 | // console.log ("aborted at " + this.index); 49 | // }, 50 | // complete: function (thread) { 51 | // console.log ("completed"); 52 | // } 53 | // }; 54 | // 55 | // var thread = Thread (test, 100); 56 | // thread.start (); 57 | // 58 | 59 | 60 | function Thread (action, interval) { 61 | "use strict"; 62 | 63 | var thread = { 64 | ref: null, 65 | interval: 10, 66 | action: null, 67 | worker: null, 68 | completed: false 69 | }; 70 | if (interval) 71 | thread.interval = interval; 72 | thread.action = action; 73 | 74 | function execute () { 75 | if (thread.completed) 76 | return; 77 | 78 | if (thread.action.execute()) { 79 | thread.completed = true; 80 | thread.action.complete(thread.ref); 81 | } else { 82 | thread.worker = setTimeout(function(){execute();}, interval); 83 | } 84 | } 85 | 86 | function finish (timeout) { 87 | if (thread.completed) 88 | return; 89 | 90 | clearTimeout(thread.worker); 91 | 92 | let terminate = function() { 93 | thread.completed = true; 94 | thread.action.finish(); 95 | thread.action.complete(thread.ref); 96 | }; 97 | 98 | if (timeout) { 99 | thread.worker = setTimeout(terminate, timeout); 100 | } else { 101 | terminate(); 102 | } 103 | } 104 | 105 | function abort () { 106 | if (thread.completed) 107 | return; 108 | 109 | clearTimeout(thread.worker); 110 | 111 | thread.completed = true; 112 | thread.action.abort(thread.ref); 113 | } 114 | 115 | thread.ref = { 116 | start: function () { 117 | execute(); 118 | }, 119 | 120 | terminate: function (timeout) { 121 | finish(); 122 | }, 123 | 124 | kill: function () { 125 | abort(); 126 | } 127 | }; 128 | 129 | return thread.ref; 130 | } 131 | -------------------------------------------------------------------------------- /webextension/web-ext-config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | ignoreFiles: ['**/*~', 'web-ext-config.js'], 4 | build: { 5 | overwriteDest: true 6 | } 7 | }; 8 | 9 | --------------------------------------------------------------------------------