Skip to content

Commit 229eb10

Browse files
committed
Merge branch 'percent-encoding' of https://github.com/c960657/Autolinker.js into c960657-percent-encoding
2 parents 616f145 + 8118a50 commit 229eb10

File tree

6 files changed

+112
-5
lines changed

6 files changed

+112
-5
lines changed

docs/examples/live-example/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ <h4>Match Types:</h4>
4141
<h4>Other Options:</h4>
4242
<div id="option-stripPrefix"></div>
4343
<div id="option-stripTrailingSlash"></div>
44+
<div id="option-decodePercentEncoding"></div>
4445
<div id="option-newWindow"></div>
4546
<br>
4647
<div id="option-truncate-length"></div>

docs/examples/live-example/live-example-all.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ var CheckboxOption = LiveExample.CheckboxOption;
261261
var RadioOption = LiveExample.RadioOption;
262262
var TextOption = LiveExample.TextOption;
263263
$(document).ready(function () {
264-
var $inputEl = $('#input'), $outputEl = $('#output'), $optionsOutputEl = $('#options-output'), urlsSchemeOption, urlsWwwOption, urlsTldOption, emailOption, phoneOption, mentionOption, hashtagOption, newWindowOption, stripPrefixOption, stripTrailingSlashOption, truncateLengthOption, truncationLocationOption, classNameOption;
264+
var $inputEl = $('#input'), $outputEl = $('#output'), $optionsOutputEl = $('#options-output'), urlsSchemeOption, urlsWwwOption, urlsTldOption, emailOption, phoneOption, mentionOption, hashtagOption, newWindowOption, stripPrefixOption, stripTrailingSlashOption, decodePercentEncodingOption, truncateLengthOption, truncationLocationOption, classNameOption;
265265
init();
266266
function init() {
267267
urlsSchemeOption = new CheckboxOption({ name: 'urls.schemeMatches', description: 'Scheme:// URLs', defaultValue: true }).onChange(autolink);
@@ -274,6 +274,7 @@ $(document).ready(function () {
274274
newWindowOption = new CheckboxOption({ name: 'newWindow', description: 'Open in new window', defaultValue: true }).onChange(autolink);
275275
stripPrefixOption = new CheckboxOption({ name: 'stripPrefix', description: 'Strip prefix', defaultValue: true }).onChange(autolink);
276276
stripTrailingSlashOption = new CheckboxOption({ name: 'stripTrailingSlash', description: 'Strip trailing slash', defaultValue: true }).onChange(autolink);
277+
decodePercentEncodingOption = new CheckboxOption({ name: 'decodePercentEncoding', description: 'Decode percent-encoding', defaultValue: true }).onChange(autolink);
277278
truncateLengthOption = new TextOption({ name: 'truncate.length', description: 'Truncate Length', size: 2, defaultValue: '0' }).onChange(autolink);
278279
truncationLocationOption = new RadioOption({ name: 'truncate.location', description: 'Truncate Location', options: ['end', 'middle', 'smart'], defaultValue: 'end' }).onChange(autolink);
279280
classNameOption = new TextOption({ name: 'className', description: 'CSS class(es)', size: 10 }).onChange(autolink);
@@ -302,6 +303,7 @@ $(document).ready(function () {
302303
newWindow: newWindowOption.getValue(),
303304
stripPrefix: stripPrefixOption.getValue(),
304305
stripTrailingSlash: stripTrailingSlashOption.getValue(),
306+
decodePercentEncoding: decodePercentEncodingOption.getValue(),
305307
className: classNameOption.getValue(),
306308
truncate: {
307309
length: +truncateLengthOption.getValue(),
@@ -324,6 +326,7 @@ $(document).ready(function () {
324326
"",
325327
(" stripPrefix : " + optionsObj.stripPrefix + ","),
326328
(" stripTrailingSlash : " + optionsObj.stripTrailingSlash + ","),
329+
(" decodePercentEncoding : " + optionsObj.decodePercentEncoding + ","),
327330
(" newWindow : " + optionsObj.newWindow + ","),
328331
"",
329332
" truncate : {",

src/Autolinker.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ var Autolinker = function( cfg ) {
121121
this.newWindow = typeof cfg.newWindow === 'boolean' ? cfg.newWindow : true;
122122
this.stripPrefix = this.normalizeStripPrefixCfg( cfg.stripPrefix );
123123
this.stripTrailingSlash = typeof cfg.stripTrailingSlash === 'boolean' ? cfg.stripTrailingSlash : true;
124+
this.decodePercentEncoding = typeof cfg.decodePercentEncoding === 'boolean' ? cfg.decodePercentEncoding : true;
124125

125126
// Validate the value of the `mention` cfg
126127
var mention = this.mention;
@@ -353,6 +354,16 @@ Autolinker.prototype = {
353354
* `http://google.com`.
354355
*/
355356

357+
/**
358+
* @cfg {Boolean} [decodePercentEncoding=true]
359+
*
360+
* `true` to decode percent-encoded characters in URL matches, `false` to keep
361+
* the percent-encoded characters.
362+
*
363+
* Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
364+
* be displayed as `https://en.wikipedia.org/wiki/San_José`.
365+
*/
366+
356367
/**
357368
* @cfg {Number/Object} [truncate=0]
358369
*
@@ -852,7 +863,7 @@ Autolinker.prototype = {
852863
new matchersNs.Email( { tagBuilder: tagBuilder } ),
853864
new matchersNs.Phone( { tagBuilder: tagBuilder } ),
854865
new matchersNs.Mention( { tagBuilder: tagBuilder, serviceName: this.mention } ),
855-
new matchersNs.Url( { tagBuilder: tagBuilder, stripPrefix: this.stripPrefix, stripTrailingSlash: this.stripTrailingSlash } )
866+
new matchersNs.Url( { tagBuilder: tagBuilder, stripPrefix: this.stripPrefix, stripTrailingSlash: this.stripTrailingSlash, decodePercentEncoding: this.decodePercentEncoding } )
856867
];
857868

858869
return ( this.matchers = matchers );

src/match/Url.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {
5050
* @inheritdoc Autolinker#cfg-stripTrailingSlash
5151
*/
5252

53+
/**
54+
* @cfg {Boolean} decodePercentEncoding (required)
55+
* @inheritdoc Autolinker#cfg-decodePercentEncoding
56+
*/
5357

5458
/**
5559
* @constructor
@@ -66,6 +70,7 @@ Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {
6670
if( cfg.protocolRelativeMatch == null ) throw new Error( '`protocolRelativeMatch` cfg required' );
6771
if( cfg.stripPrefix == null ) throw new Error( '`stripPrefix` cfg required' );
6872
if( cfg.stripTrailingSlash == null ) throw new Error( '`stripTrailingSlash` cfg required' );
73+
if( cfg.decodePercentEncoding == null ) throw new Error( '`decodePercentEncoding` cfg required' );
6974
// @endif
7075

7176
this.urlMatchType = cfg.urlMatchType;
@@ -74,6 +79,7 @@ Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {
7479
this.protocolRelativeMatch = cfg.protocolRelativeMatch;
7580
this.stripPrefix = cfg.stripPrefix;
7681
this.stripTrailingSlash = cfg.stripTrailingSlash;
82+
this.decodePercentEncoding = cfg.decodePercentEncoding;
7783
},
7884

7985

@@ -192,6 +198,9 @@ Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {
192198
if( this.stripTrailingSlash ) {
193199
anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one
194200
}
201+
if( this.decodePercentEncoding ) {
202+
anchorText = this.removePercentEncoding( anchorText);
203+
}
195204

196205
return anchorText;
197206
},
@@ -254,6 +263,28 @@ Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {
254263
anchorText = anchorText.slice( 0, -1 );
255264
}
256265
return anchorText;
266+
},
267+
268+
/**
269+
* Decodes percent-encoded characters from the given `anchorText`, in preparation for the text to be displayed.
270+
*
271+
* @private
272+
* @param {String} anchorText The text of the anchor that is being generated, for which to decode any percent-encoded characters.
273+
* @return {String} The `anchorText`, with the percent-encoded characters decoded.
274+
*/
275+
removePercentEncoding : function( anchorText ) {
276+
try {
277+
return decodeURIComponent( anchorText
278+
.replace( /%22/gi, '&quot;' )
279+
.replace( /%26/gi, '&amp;' )
280+
.replace( /%27/gi, '&#39;')
281+
.replace( /%3C/gi, '&lt;' )
282+
.replace( /%3E/gi, '&gt;' )
283+
);
284+
} catch (e) {
285+
// Invalid escape sequence.
286+
return anchorText;
287+
}
257288
}
258289

259290
} );

src/matcher/Url.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ Autolinker.matcher.Url = Autolinker.Util.extend( Autolinker.matcher.Matcher, {
2020
* @inheritdoc Autolinker#stripTrailingSlash
2121
*/
2222

23+
/**
24+
* @cfg {Boolean} decodePercentEncoding (required)
25+
* @inheritdoc Autolinker#decodePercentEncoding
26+
*/
27+
2328

2429
/**
2530
* @private
@@ -155,6 +160,7 @@ Autolinker.matcher.Url = Autolinker.Util.extend( Autolinker.matcher.Matcher, {
155160

156161
this.stripPrefix = cfg.stripPrefix;
157162
this.stripTrailingSlash = cfg.stripTrailingSlash;
163+
this.decodePercentEncoding = cfg.decodePercentEncoding;
158164
},
159165

160166

@@ -165,6 +171,7 @@ Autolinker.matcher.Url = Autolinker.Util.extend( Autolinker.matcher.Matcher, {
165171
var matcherRegex = this.matcherRegex,
166172
stripPrefix = this.stripPrefix,
167173
stripTrailingSlash = this.stripTrailingSlash,
174+
decodePercentEncoding = this.decodePercentEncoding,
168175
tagBuilder = this.tagBuilder,
169176
matches = [],
170177
match;
@@ -227,7 +234,8 @@ Autolinker.matcher.Url = Autolinker.Util.extend( Autolinker.matcher.Matcher, {
227234
protocolUrlMatch : protocolUrlMatch,
228235
protocolRelativeMatch : !!protocolRelativeMatch,
229236
stripPrefix : stripPrefix,
230-
stripTrailingSlash : stripTrailingSlash
237+
stripTrailingSlash : stripTrailingSlash,
238+
decodePercentEncoding : decodePercentEncoding,
231239
} ) );
232240
}
233241

tests/AutolinkerSpec.js

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ describe( "Autolinker", function() {
762762

763763
it( "should include escaped parentheses in the URL", function() {
764764
var result = autolinker.link( "Here's an example from CodingHorror: http://en.wikipedia.org/wiki/PC_Tools_%28Central_Point_Software%29" );
765-
expect( result ).toBe( 'Here\'s an example from CodingHorror: <a href="http://en.wikipedia.org/wiki/PC_Tools_%28Central_Point_Software%29">en.wikipedia.org/wiki/PC_Tools_%28Central_Point_Software%29</a>' );
765+
expect( result ).toBe( 'Here\'s an example from CodingHorror: <a href="http://en.wikipedia.org/wiki/PC_Tools_%28Central_Point_Software%29">en.wikipedia.org/wiki/PC_Tools_(Central_Point_Software)</a>' );
766766
} );
767767

768768
} );
@@ -818,6 +818,20 @@ describe( "Autolinker", function() {
818818
expect( result ).toBe( '<a href="http://google.no/maps/place/Gary\'s+Deli/@52.3664378,4.869345,18z/data=!4m7!1m4!3m3!1s0x47c609c14a6680df:0x643f005113531f15!2sBeertemple!3b1!3m1!1s0x0000000000000000:0x51a8a6adb4307be6?hl=no">google.no/maps/place/Gary\'s+Deli/@52.3664378,4.869345,18z/data=!4m7!1m4!3m3!1s0x47c609c14a6680df:0x643f005113531f15!2sBeertemple!3b1!3m1!1s0x0000000000000000:0x51a8a6adb4307be6?hl=no</a>' );
819819
} );
820820

821+
822+
it( "should decode emojis", function() {
823+
var result = autolinker.link( "Danish flag emoji: https://emojipedia.org/%F0%9F%87%A9%F0%9F%87%B0" );
824+
825+
expect( result ).toBe( 'Danish flag emoji: <a href="https://emojipedia.org/%F0%9F%87%A9%F0%9F%87%B0">emojipedia.org/🇩🇰</a>' );
826+
} );
827+
828+
829+
it( "should HTML-encode escape-encoded special characters", function() {
830+
var result = autolinker.link( "Link: http://example.com/%3c%3E%22%27%26" );
831+
832+
expect( result ).toBe( 'Link: <a href="http://example.com/%3c%3E%22%27%26">example.com/&lt;&gt;&quot;&#39;&amp;</a>' );
833+
} );
834+
821835
} );
822836

823837

@@ -861,7 +875,7 @@ describe( "Autolinker", function() {
861875

862876
it( "should automatically link a URL with a complex hash (such as a Google Analytics url)", function() {
863877
var result = autolinker.link( "Joe went to https://www.google.com/analytics/web/?pli=1#my-reports/Obif-Y6qQB2xAJk0ZZE1Zg/a4454143w36378534p43704543/%3F.date00%3D20120314%26_.date01%3D20120314%268534-table.rowStart%3D0%268534-table.rowCount%3D25/ and analyzed his analytics" );
864-
expect( result ).toBe( 'Joe went to <a href="https://www.google.com/analytics/web/?pli=1#my-reports/Obif-Y6qQB2xAJk0ZZE1Zg/a4454143w36378534p43704543/%3F.date00%3D20120314%26_.date01%3D20120314%268534-table.rowStart%3D0%268534-table.rowCount%3D25/">google.com/analytics/web/?pli=1#my-reports/Obif-Y6qQB2xAJk0ZZE1Zg/a4454143w36378534p43704543/%3F.date00%3D20120314%26_.date01%3D20120314%268534-table.rowStart%3D0%268534-table.rowCount%3D25</a> and analyzed his analytics' );
878+
expect( result ).toBe( 'Joe went to <a href="https://www.google.com/analytics/web/?pli=1#my-reports/Obif-Y6qQB2xAJk0ZZE1Zg/a4454143w36378534p43704543/%3F.date00%3D20120314%26_.date01%3D20120314%268534-table.rowStart%3D0%268534-table.rowCount%3D25/">google.com/analytics/web/?pli=1#my-reports/Obif-Y6qQB2xAJk0ZZE1Zg/a4454143w36378534p43704543/?.date00=20120314&amp;_.date01=20120314&amp;8534-table.rowStart=0&amp;8534-table.rowCount=25</a> and analyzed his analytics' );
865879
} );
866880

867881

@@ -1729,6 +1743,7 @@ describe( "Autolinker", function() {
17291743
expect( result ).toBe( tobe );
17301744
} );
17311745

1746+
17321747
} );
17331748

17341749

@@ -1902,6 +1917,44 @@ describe( "Autolinker", function() {
19021917
} );
19031918

19041919

1920+
describe( "`decodePercentEncoding` option", function() {
1921+
1922+
it( "by default, should decode percent-encoding", function() {
1923+
var result = Autolinker.link( "https://en.wikipedia.org/wiki/San_Jos%C3%A9", {
1924+
stripPrefix : false,
1925+
//decodePercentEncoding : true, -- not providing this cfg
1926+
newWindow : false
1927+
} );
1928+
1929+
expect( result ).toBe( '<a href="https://en.wikipedia.org/wiki/San_Jos%C3%A9">https://en.wikipedia.org/wiki/San_José</a>' );
1930+
} );
1931+
1932+
1933+
it( "when provided as `true`, should decode percent-encoding", function() {
1934+
var result = Autolinker.link( "https://en.wikipedia.org/wiki/San_Jos%C3%A9", {
1935+
stripPrefix : false,
1936+
decodePercentEncoding : true,
1937+
newWindow : false
1938+
} );
1939+
1940+
expect( result ).toBe( '<a href="https://en.wikipedia.org/wiki/San_Jos%C3%A9">https://en.wikipedia.org/wiki/San_José</a>' );
1941+
} );
1942+
1943+
1944+
it( "when provided as `false`, should not decode percent-encoding",
1945+
function() {
1946+
var result = Autolinker.link( "https://en.wikipedia.org/wiki/San_Jos%C3%A9", {
1947+
stripPrefix : false,
1948+
decodePercentEncoding : false,
1949+
newWindow : false
1950+
} );
1951+
1952+
expect( result ).toBe( '<a href="https://en.wikipedia.org/wiki/San_Jos%C3%A9">https://en.wikipedia.org/wiki/San_Jos%C3%A9</a>' );
1953+
} );
1954+
1955+
} );
1956+
1957+
19051958
describe( "`truncate` option", function() {
19061959

19071960
describe( 'number form', function() {

0 commit comments

Comments
 (0)