Merge branch 'develop' into 'master'

Develop

See merge request nobody42/localcdn!21
This commit is contained in:
nobody 2020-05-16 16:59:40 +00:00
commit 7588476acd
23 changed files with 404 additions and 36 deletions

View File

@ -7,7 +7,8 @@ A web browser extension that emulates Content Delivery Networks to improve your
LocalCDN based on Decentraleyes. It includes more frameworks and more CDNs:
* **New: Font Awesome v4.7.0 and v5.7.2** :tada: :tada: :tada:
* **NEW: Removed integrity checks of embedded script and style elements** :tada: :tada: :tada:
* Font Awesome
* jQuery up to 3.4.1
* Bootstrap CSS (Delivered by StackPath, NetDNA and MaxCDN)
* Bootstrap JavaScript (Delivered by StackPath, NetDNA and MaxCDN)
@ -16,6 +17,20 @@ LocalCDN based on Decentraleyes. It includes more frameworks and more CDNs:
> **Note:** LocalCDN is no silver bullet, but it does prevent a lot of websites from making you send these kinds of requests. Ultimately, you can make LocalCDN block requests for any missing CDN resources, too.
## What is the different of LocalCDN in comparison to other CDN emulators?
<img src="/screenshots/replacement.png?raw=true" alt="The Replacement of Libraries">
**Advantages of LocalCDN:**
:thumbsup: more frameworks/libraries
:thumbsup: smaller size than other extensions
:thumbsup: remove crossorigin and integrity attributes of script and stylesheet tags to increase replacements
:thumbsup: doesn't matter which version a website requested
## We need you!
![We Need You!](/pages/welcome/we-need-you.png?raw=true "We Need You!")
@ -64,6 +79,11 @@ Please read this [developer guide](https://developer.mozilla.org/en-US/Add-ons/W
> **Important:** All tagged commits are signed with GPG. It's likely best to ignore unsigned commits, unless you really know what you're doing. Please send an email if you have any questions or security concerns.
## Contact
Just open an issue with your question or write an [email](https://localcdn.de/contact/) (PGP possible!).
## Donations
LocalCDN is free, open-source and based on Decentraleyes. If you like LocalCDN and/or Decentraleyes you can support continued development by making a donation. Any help would be greatly appreciated!

View File

@ -100,11 +100,11 @@
"description": "If enabled, you wont receive any information about new features in LocalCDN. This includes information about new uBlock/uMatrix rules."
},
"featureBreaksWebsitesDescription": {
"message": "",
"message": "This feature breaks websites. Do not leave it enabled, unless you are prepared to manually whitelist any affected domains.",
"description": "This feature breaks websites. Do not leave it enabled, unless you are prepared to manually whitelist any affected domains."
},
"featureBreaksWebsitesButton": {
"message": "",
"message": "Disable",
"description": "Disable"
}
}

View File

@ -1,6 +1,6 @@
{
"extensionDescription": {
"message": "Protegge dal tracciamento tramite CDN centralizzato \"gratis\".",
"message": "Protegge dal tracciamento tramite CDN centralizzati \"liberi\".",
"description": "Extension description."
},
"disableProtectionTitle": {
@ -16,7 +16,7 @@
"description": "Amount injected title."
},
"amountInjectedDescription": {
"message": "Quantità di iniezioni di risorse dalla Rete di Distribuzione di Contenuti locale sin dall'installazione.",
"message": "Quantità di iniezioni di risorse dalla CDN locale sin dall'installazione.",
"description": "Amount injected description."
},
"optionsTitle": {
@ -32,7 +32,7 @@
"description": "Show icon badge description."
},
"blockMissingTitle": {
"message": "Bloccare richieste di risorse mancanti",
"message": "Blocca le richieste di risorse mancanti",
"description": "Block requests for missing resources title."
},
"blockMissingDescription": {
@ -40,27 +40,27 @@
"description": "Block requests for missing resources description."
},
"disablePrefetchTitle": {
"message": "Disabilita il prefetching dei link",
"message": "Disabilita il precaricamento dei collegamenti",
"description": "Disable prefetch title."
},
"disablePrefetchDescription": {
"message": "Impedisci alle richieste vietate di informare le reti di consegna.",
"message": "Impedisci alle richieste non consentite di uscire dalle reti di distribuzione.",
"description": "Disable prefetch description."
},
"stripMetadataTitle": {
"message": "Elimina i metadata dalle richieste consentite",
"message": "Togli i metadati dalle richieste consentite",
"description": "Strip metadata title."
},
"stripMetadataDescription": {
"message": "Cancella i dati sensibili dalle richieste CDN consentite per una migliore privacy.",
"message": "Cancella i dati sensibili dalle richieste consentite alle CDN per migliorare la privacy.",
"description": "Strip metadata description."
},
"whitelistedDomainsTitle": {
"message": "Escludere domini dalle ispezioni",
"message": "Escludi i domini dalle ispezioni",
"description": "Whitelisted domains title."
},
"whitelistedDomainsDescription": {
"message": "Inserire domini nella whitelist per escluderli. Separare voci multiple con punti e virgola (;).",
"message": "Inserisci i domini nella whitelist per escluderli. Separa le voci multiple con punti e virgola (;).",
"description": "Whitelisted domains description."
},
"advancedLabel": {
@ -68,43 +68,43 @@
"description": "Advanced label."
},
"generateRuleSetTitle": {
"message": "Genera set di regole per uBlock o uMatrix",
"message": "Genera gruppo di regole per uBlock o uMatrix",
"description": "Generate rule set title."
},
"generateRuleSetDescription": {
"message": "",
"message": "Nel caso stessi utilizzando uBlock o uMatrix è possibile generare le regole qui. È necessario aggiungere queste regole manualmente in uBlock o uMatrix.",
"description": "In case you're using uBlock or uMatrix you can generate the rules here. You have to add these rules manually in uBlock or uMatrix."
},
"lastUpdate": {
"message": "",
"message": "Ultimo aggiornamento:",
"description": "Last update."
},
"copyRuleSet": {
"message": "",
"message": "Copia",
"description": "Text of button to copy ruleset."
},
"loggingTitle": {
"message": "",
"message": "Abilita l'accesso alla console del browser",
"description": "Enable logging in browser console."
},
"loggingDescription": {
"message": "",
"message": "Apri la Console del Browser ( CTRL + SHIFT + J ) per mostrare le risorse mancanti",
"description": "Open \"Browser Console\" ( CTRL + SHIFT + J ) to show missing resources."
},
"hideReleaseNotesTitle": {
"message": "",
"message": "Disabilita le note di rilascio",
"description": "Disable release notes"
},
"hideReleaseNotesDescription": {
"message": "",
"message": "Se abilitato, non riceverai alcuna informazione sulle nuove funzionalità di LocalCDN. Questo include informazioni sulle nuove regole per uBlock/uMatrix.",
"description": "If enabled, you wont receive any information about new features in LocalCDN. This includes information about new uBlock/uMatrix rules."
},
"featureBreaksWebsitesDescription": {
"message": "",
"message": "Questa funzione rompe i siti web. Non lasciarla abilitata, a meno che tu non sia pronto a mettere manualmente nella whitelist i domini interessati.",
"description": "This feature breaks websites. Do not leave it enabled, unless you are prepared to manually whitelist any affected domains."
},
"featureBreaksWebsitesButton": {
"message": "",
"message": "Disabilita",
"description": "Disable"
}
}

View File

@ -72,39 +72,39 @@
"description": "Generate rule set title."
},
"generateRuleSetDescription": {
"message": "",
"message": "Jeśli używasz uBlock lub uMatrix, to możesz wygenerować tutaj odpowiednie reguły. Musisz je potem dodać ręcznie w uBlock albo uMatrix.",
"description": "In case you're using uBlock or uMatrix you can generate the rules here. You have to add these rules manually in uBlock or uMatrix."
},
"lastUpdate": {
"message": "",
"message": "Ostatnia aktualizacja: ",
"description": "Last update."
},
"copyRuleSet": {
"message": "",
"message": "Kopiuj",
"description": "Text of button to copy ruleset."
},
"loggingTitle": {
"message": "",
"message": "Włącz logowanie w konsoli przeglądarki.",
"description": "Enable logging in browser console."
},
"loggingDescription": {
"message": "",
"message": "Otwórz \"Konsolę Przeglądarki\" ( CTRL + SHIFT + J) żeby wyświetlić brakujące zasoby.",
"description": "Open \"Browser Console\" ( CTRL + SHIFT + J ) to show missing resources."
},
"hideReleaseNotesTitle": {
"message": "",
"message": "Wyłącz informacje o zmianach",
"description": "Disable release notes"
},
"hideReleaseNotesDescription": {
"message": "",
"message": "Po włączeniu nie będziesz otrzymywać żadnych informacji o nowych funkcjach w LocalCDN. W tym informacji o nowych regułach uBlock/uMatrix.",
"description": "If enabled, you wont receive any information about new features in LocalCDN. This includes information about new uBlock/uMatrix rules."
},
"featureBreaksWebsitesDescription": {
"message": "",
"message": "Ta opcja psuje strony. Nie włączaj jej, chyba że jesteś gotów samemu dodawać niedziałające domeny do białej listy.",
"description": "This feature breaks websites. Do not leave it enabled, unless you are prepared to manually whitelist any affected domains."
},
"featureBreaksWebsitesButton": {
"message": "",
"message": "Wyłącz",
"description": "Disable"
}
}

View File

@ -92,4 +92,17 @@ const Whitelist = {
const BrowserType = {
'CHROMIUM': chrome.runtime.getURL("/").startsWith("chrome-extension"),
'FIREFOX': chrome.runtime.getURL("/").startsWith("moz-extension")
}
};
const CharsetDomains = {
'dejure.org': 'iso-8859-1',
'privacy-handbuch.de': 'iso-8859-1',
'winfuture.de': 'iso-8859-1',
'drwindows.de': 'iso-8859-1',
'sphinx-soft.com': 'iso-8859-1',
'ekaterinaguseva.ru': 'windows-1251',
'hobbybrauerversand.de': 'iso-8859-1',
'pro-linux.de': 'iso-8859-15',
'wwwuser.gwdg.de': 'windows-1252',
'tyurem.net': 'windows-1251'
};

View File

@ -250,6 +250,10 @@ var files = {
// lozad.js
'resources/lozad.js/1.14.0/lozad.min.jsm': true,
// Material Design for Bootstrap
'resources/mdbootstrap/4.18.0/js/mdb.min.jsm': true,
'resources/mdbootstrap/4.18.0/css/mdb.min.css': true,
// Modernizr
'resources/modernizr/2.8.3/modernizr.min.jsm': true,
@ -313,6 +317,9 @@ var files = {
// SWFObject
'resources/swfobject/2.2/swfobject.jsm': true,
// Tether JS
'resources/tether/1.4.7/js/tether.min.jsm': true,
// toastr.js
'resources/toastr.js/2.1.4/toastr.min.css': true,
'resources/toastr.js/2.1.4/toastr.min.jsm': true,
@ -320,6 +327,8 @@ var files = {
// Twitter Bootstrap
'resources/twitter-bootstrap/3.4.1/js/bootstrap.min.jsm': true,
'resources/twitter-bootstrap/3.4.1/css/bootstrap.min.css': true,
'resources/twitter-bootstrap/4.5.0/js/bootstrap.min.jsm': true,
'resources/twitter-bootstrap/4.5.0/css/bootstrap.min.css': true,
// Underscore.js
'resources/underscore.js/1.8.3/underscore-min.jsm': true,

View File

@ -26,6 +26,18 @@ var manipulateDOM = {};
* Private Methods
*/
manipulateDOM._getEncoding = function (domain) {
let encodingByDomain = CharsetDomains[domain];
if(typeof encodingByDomain === 'undefined') {
return 'UTF-8';
}
return encodingByDomain;
};
manipulateDOM._removeCrossOriginAndIntegrityAttr = function (details) {
// by Jaap (https://gitlab.com/Jaaap)
@ -44,7 +56,7 @@ manipulateDOM._removeCrossOriginAndIntegrityAttr = function (details) {
if (!isWhitelisted && mimeType === 'text/html') {
header.value = 'text/html; charset=UTF-8';
let decoder = new TextDecoder(charset);
let decoder = new TextDecoder(manipulateDOM._getEncoding(initiatorDomain));
let encoder = new TextEncoder();
let filter = browser.webRequest.filterResponseData(details.requestId);

View File

@ -135,6 +135,8 @@ var mappings = {
'js-cookie/{version}/js.cookie.min.js': resources.jscookie,
'lazysizes/{version}/lazysizes.min.js': resources.lazysizes,
'lodash.js/{version}/lodash.': resources.lodashJS,
'mdbootstrap/{version}/js/mdb.': resources.mdbootstrapJS,
'mdbootstrap/{version}/css/mdb.': resources.mdbootstrapCSS,
'modernizr/{version}/modernizr.': resources.modernizr,
'moment.js/{version}/moment.': resources.moment,
'moment.js/{version}/moment.min.': resources.moment,
@ -155,9 +157,12 @@ var mappings = {
'spin.js/{version}/spin.min.js': resources.spinJS,
'socket.io/{version}/socket.io.': resources.socketIO,
'swfobject/{version}/swfobject.': resources.swfobject,
'tether/{version}/js/tether.': resources.tetherJS,
'toastr.js/{version}/toastr.min.css': resources.toastrCSS,
'toastr.js/{version}/toastr.min.js': resources.toastrJS,
'twitter-bootstrap/{version}/js/bootstrap.min.js': resources.twitterBootstrapJS,
'twitter-bootstrap/{version}-alpha.3/js/bootstrap.min.js': resources.twitterBootstrapJS,
'twitter-bootstrap/{version}-alpha.3/css/bootstrap.min.css': resources.twitterBootstrapCSS,
'twitter-bootstrap/{version}/css/bootstrap.': resources.twitterBootstrapCSS,
'underscore.js/{version}/underscore.': resources.underscore,
'underscore.js/{version}/underscore-min.': resources.underscore,

View File

@ -309,6 +309,15 @@ var resources = {
'path': 'resources/lozad.js/{version}/lozad.min.jsm',
'type': 'application/javascript'
},
// Material Design for Bootstrap
'mdbootstrapJS': {
'path': 'resources/mdbootstrap/{version}/js/mdb.min.jsm',
'type': 'application/javascript'
},
'mdbootstrapCSS': {
'path': 'resources/mdbootstrap/{version}/css/mdb.min.css',
'type': 'text/css'
},
// Modernizr
'modernizr': {
'path': 'resources/modernizr/{version}/modernizr.min.jsm',
@ -421,6 +430,11 @@ var resources = {
'path': 'resources/swfobject/{version}/swfobject.jsm',
'type': 'application/javascript'
},
// Tether JS
'tetherJS': {
'path': 'resources/tether/{version}/js/tether.min.jsm',
'type': 'application/javascript'
},
// Twitter Bootstrap JS
'twitterBootstrapJS': {
'path': 'resources/twitter-bootstrap/{version}/js/bootstrap.min.jsm',

View File

@ -3,3 +3,7 @@ enabled.svg (edited)
settings-dark.svg, settings-light.svg (edited)
https://www.svgrepo.com/vectors/web-security-fill/
Creative Commons BY 4.0
donate.svg (edited)
https://www.svgrepo.com/svg/39500/heart
CC0

124
icons/donate.svg Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Capa_1"
x="0px"
y="0px"
viewBox="0 0 13.066 13.066"
style="enable-background:new 0 0 13.066 13.066;"
xml:space="preserve"
sodipodi:docname="donate.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
id="metadata47"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs45" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="997"
id="namedview43"
showgrid="false"
inkscape:zoom="18.062146"
inkscape:cx="6.6437288"
inkscape:cy="6.533"
inkscape:window-x="1680"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />
<g
id="g10"
style="fill:#d75a4a;fill-opacity:1">
<g
id="g8"
style="fill:#d75a4a;fill-opacity:1">
<g
id="g6"
style="fill:#d75a4a;fill-opacity:1">
<g
id="g4"
style="fill:#d75a4a;fill-opacity:1">
<path
style="fill:#d75a4a;fill-opacity:1"
d="M6.555,12.558c-0.098,0-0.195-0.034-0.273-0.103c-0.233-0.2-5.718-4.954-6.199-7.885 C-0.133,3.243,0.071,2.201,0.69,1.474C1.22,0.85,2.034,0.507,2.982,0.507c0.082,0,0.165,0.002,0.247,0.008 c0.058-0.003,0.115-0.004,0.172-0.004c1.048,0,2.343,0.461,3.109,2.421c0.43-1.196,1.311-2.417,3.328-2.417 c1.135,0,2.023,0.342,2.571,0.987c0.597,0.701,0.787,1.733,0.569,3.068c-0.479,2.929-5.918,7.684-6.149,7.884 C6.751,12.524,6.653,12.558,6.555,12.558z"
id="path2" />
</g>
</g>
</g>
</g>
<g
id="g12"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g14"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g16"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g18"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g20"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g22"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g24"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g26"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g28"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g30"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g32"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g34"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g36"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g38"
style="fill:#d75a4a;fill-opacity:1">
</g>
<g
id="g40"
style="fill:#d75a4a;fill-opacity:1">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "LocalCDN (fork from Decentraleyes)",
"version": "2.2.0",
"version": "2.2.1",
"browser_specific_settings": {
"gecko": {
"id": "{b86e4813-687a-43e6-ab65-0bde4ab75758}",

View File

@ -324,6 +324,10 @@ helpers.determineResourceName = function (filename) {
return 'Lodash';
case 'lozad.min.jsm':
return 'lozad.js';
case 'mdb.min.css':
return 'MDBootstrap (CSS)';
case 'mdb.min.jsm':
return 'MDBootstrap (JS)';
case 'modernizr.min.jsm':
return 'Modernizr';
case 'moment.min.jsm':
@ -370,6 +374,8 @@ helpers.determineResourceName = function (filename) {
return 'Store.js';
case 'swfobject.jsm':
return 'SWFObject';
case 'tether.min.jsm':
return 'Tether JS';
case 'toastr.min.css':
return 'toastr.js';
case 'toastr.min.jsm':
@ -563,6 +569,8 @@ helpers.setLastVersion = function (type, version) {
version = '4.17.10';
} else if (type.includes('lozad')) {
version = '1.14.0';
} else if (type.includes('/mdbootstrap/4.')) {
version = '4.18.0';
} else if (type.includes('/modernizr/2.')) {
version = '2.8.3';
} else if (type.includes('/moment.js/2.')) {
@ -601,6 +609,10 @@ helpers.setLastVersion = function (type, version) {
version = '2.0.4';
} else if (type.includes('/swfobject/2.')) {
version = '2.2';
} else if (type.includes('/tether/1.')) {
version = '1.4.7';
} else if (type.includes('/twitter-bootstrap/4.')) {
version = '4.5.0';
} else if (type.includes('/twitter-bootstrap/3.')) {
version = '3.4.1';
} else if (type.includes('/toastr.js/2.')) {

View File

@ -20,6 +20,7 @@ header {
border-bottom: solid #d3d3d3 1px;
display: flex;
position: relative;
padding: 8px;
}
.panel {
@ -76,6 +77,7 @@ footer {
font-size: 14px;
font-weight: 600;
padding-left: 0;
width: 100%;
}
.subheading {
@ -177,7 +179,8 @@ footer {
color: #339a6f;
}
#options-button-svg {
#options-button-svg,
#donate-button-svg {
background-size: cover;
width: 15px;
height: 15px;
@ -187,6 +190,10 @@ footer {
background-image: url("../../icons/settings-dark.svg");
}
#donate-button-svg {
background-image: url("../../icons/donate.svg");
}
#protection-toggle {
-moz-user-select: none;
cursor: pointer;

View File

@ -27,6 +27,10 @@
<img class="icon-logo" src="icon.svg" alt="Extension Icon">
<div class="heading">LocalCDN <sup id="version-label" class="label-version"></sup></div>
<div id="donate-button" class="button">
<div id="donate-button-svg"></div>
</div>
</header>
<section class="content">

View File

@ -39,18 +39,20 @@ popup._renderContents = function () {
popup._renderNonContextualContents = function () {
let versionLabelElement, counterElement, testingUtilityLinkElement, optionsButtonElement;
let versionLabelElement, counterElement, testingUtilityLinkElement, optionsButtonElement, donationButtonElement;
versionLabelElement = document.getElementById('version-label');
counterElement = document.getElementById('injection-counter');
testingUtilityLinkElement = document.getElementById('testing-utility-link');
optionsButtonElement = document.getElementById('options-button');
donationButtonElement = document.getElementById('donate-button');
versionLabelElement.innerText = popup._version;
counterElement.innerText = helpers.formatNumber(popup._amountInjected);
testingUtilityLinkElement.addEventListener('mouseup', popup._onTestingUtilityLinkClicked);
optionsButtonElement.addEventListener('mouseup', popup._onOptionsButtonClicked);
donationButtonElement.addEventListener('mouseup', popup._onDonationButtonClicked);
};
popup._renderContextualContents = function () {
@ -356,6 +358,20 @@ popup._onOptionsButtonClicked = function () {
return window.close();
};
popup._onDonationButtonClicked = function () {
if (event.button === 0 || event.button === 1) {
chrome.tabs.create({
'url': 'https://localcdn.de/donate/',
'active': (event.button === 0)
});
}
if (event.button === 0) {
window.close();
}
};
popup._onProtectionToggled = function () {
let bypassCache = (typeof browser === 'undefined');

View File

@ -21,6 +21,14 @@
<div class="subtle-hint">
<div class="topic-label">
New in LocalCDN:
<ul>
<li>Fixed: Encoding problem with some websites (maybe only temporary, because at the moment only 9 websites are affected) (<a href="https://gitlab.com/nobody42/localcdn/-/issues/75">#75</a>)</li>
<li>Added: Twitter Bootstrap JS and CSS v4.5.0 (<a href="https://gitlab.com/nobody42/localcdn/-/issues/77">#77</a>)</li>
<li>Added: Donation button</li>
<li>Added: Material Design for Bootstrap (MDB) v4.18.0 (<a href="https://gitlab.com/nobody42/localcdn/-/issues/77">#77</a>)</li>
<li>Added: Tether v1.4.7 (<a href="https://gitlab.com/nobody42/localcdn/-/issues/77">#77</a>)</li>
<li>Addition to the encoding problem (<a href="https://gitlab.com/nobody42/localcdn/-/issues/75">#75</a>)</li>
</ul>
</div>
<ul>
<li>Fixed typo in urlize</li>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
screenshots/replacement.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB