1/** 2 * @file 3 * External links js file. 4 */ 5 6(function ($, Drupal, drupalSettings) { 7 8 'use strict'; 9 10 Drupal.extlink = Drupal.extlink || {}; 11 12 Drupal.extlink.attach = function (context, drupalSettings) { 13 if (typeof drupalSettings.data === 'undefined' || !drupalSettings.data.hasOwnProperty('extlink')) { 14 return; 15 } 16 17 // Define the jQuery method (either 'append' or 'prepend') of placing the 18 // icon, defaults to 'append'. 19 var extIconPlacement = 'append'; 20 if (drupalSettings.data.extlink.extIconPlacement && drupalSettings.data.extlink.extIconPlacement != '0') { 21 extIconPlacement = drupalSettings.data.extlink.extIconPlacement; 22 } 23 24 // Strip the host name down, removing ports, subdomains, or www. 25 var pattern = /^(([^\/:]+?\.)*)([^\.:]{1,})((\.[a-z0-9]{1,253})*)(:[0-9]{1,5})?$/; 26 var host = window.location.host.replace(pattern, '$2$3$6'); 27 var subdomain = window.location.host.replace(host, ''); 28 29 // Determine what subdomains are considered internal. 30 var subdomains; 31 if (drupalSettings.data.extlink.extSubdomains) { 32 subdomains = '([^/]*\\.)?'; 33 } 34 else if (subdomain === 'www.' || subdomain === '') { 35 subdomains = '(www\\.)?'; 36 } 37 else { 38 subdomains = subdomain.replace('.', '\\.'); 39 } 40 41 // Whitelisted domains. 42 var whitelistedDomains = false; 43 if (drupalSettings.data.extlink.whitelistedDomains) { 44 whitelistedDomains = []; 45 for (var i = 0; i < drupalSettings.data.extlink.whitelistedDomains.length; i++) { 46 whitelistedDomains.push(new RegExp('^https?:\\/\\/' + drupalSettings.data.extlink.whitelistedDomains[i].replace(/(\r\n|\n|\r)/gm,'') + '.*$', 'i')); 47 } 48 } 49 50 // Build regular expressions that define an internal link. 51 var internal_link = new RegExp('^https?://([^@]*@)?' + subdomains + host, 'i'); 52 53 // Extra internal link matching. 54 var extInclude = false; 55 if (drupalSettings.data.extlink.extInclude) { 56 extInclude = new RegExp(drupalSettings.data.extlink.extInclude.replace(/\\/, '\\'), 'i'); 57 } 58 59 // Extra external link matching. 60 var extExclude = false; 61 if (drupalSettings.data.extlink.extExclude) { 62 extExclude = new RegExp(drupalSettings.data.extlink.extExclude.replace(/\\/, '\\'), 'i'); 63 } 64 65 // Extra external link CSS selector exclusion. 66 var extCssExclude = false; 67 if (drupalSettings.data.extlink.extCssExclude) { 68 extCssExclude = drupalSettings.data.extlink.extCssExclude; 69 } 70 71 // Extra external link CSS selector explicit. 72 var extCssExplicit = false; 73 if (drupalSettings.data.extlink.extCssExplicit) { 74 extCssExplicit = drupalSettings.data.extlink.extCssExplicit; 75 } 76 77 // Find all links which are NOT internal and begin with http as opposed 78 // to ftp://, javascript:, etc. other kinds of links. 79 // When operating on the 'this' variable, the host has been appended to 80 // all links by the browser, even local ones. 81 // In jQuery 1.1 and higher, we'd use a filter method here, but it is not 82 // available in jQuery 1.0 (Drupal 5 default). 83 var external_links = []; 84 var mailto_links = []; 85 $('a:not([data-extlink]), area:not([data-extlink])', context).each(function (el) { 86 try { 87 var url = ''; 88 if (typeof this.href == 'string') { 89 url = this.href.toLowerCase(); 90 } 91 // Handle SVG links (xlink:href). 92 else if (typeof this.href == 'object') { 93 url = this.href.baseVal; 94 } 95 if (url.indexOf('http') === 0 96 && ((!internal_link.test(url) && !(extExclude && extExclude.test(url))) || (extInclude && extInclude.test(url))) 97 && !(extCssExclude && $(this).is(extCssExclude)) 98 && !(extCssExclude && $(this).parents(extCssExclude).length > 0) 99 && !(extCssExplicit && $(this).parents(extCssExplicit).length < 1)) { 100 var match = false; 101 if (whitelistedDomains) { 102 for (var i = 0; i < whitelistedDomains.length; i++) { 103 if (whitelistedDomains[i].test(url)) { 104 match = true; 105 break; 106 } 107 } 108 } 109 if (!match) { 110 external_links.push(this); 111 } 112 }
113 // Do not include area tags with begin with mailto: (this prohibits 114 // icons from being added to image-maps). 115 else if (this.tagName !== 'AREA' 116 && url.indexOf('mailto:') === 0 117 && !(extCssExclude && $(this).parents(extCssExclude).length > 0) 118 && !(extCssExplicit && $(this).parents(extCssExplicit).length < 1)) { 119 mailto_links.push(this); 120 } 121 } 122 // IE7 throws errors often when dealing with irregular links, such as: 123 // <a href="node/10"></a> Empty tags. 124 // <a href="http://user:[email protected]">example</a> User:pass syntax. 125 catch (error) { 126 return false; 127 } 128 }); 129 130 if (drupalSettings.data.extlink.extClass !== '0' && drupalSettings.data.extlink.extClass !== '') { 131 Drupal.extlink.applyClassAndSpan(external_links, drupalSettings.data.extlink.extClass, extIconPlacement); 132 } 133 134 if (drupalSettings.data.extlink.mailtoClass !== '0' && drupalSettings.data.extlink.mailtoClass !== '') { 135 Drupal.extlink.applyClassAndSpan(mailto_links, drupalSettings.data.extlink.mailtoClass, extIconPlacement); 136 } 137 138 if (drupalSettings.data.extlink.extTarget) { 139 // Apply the target attribute to all links. 140 $(external_links).filter(function () { 141 // Filter out links with target set if option specified. 142 return !(drupalSettings.data.extlink.extTargetNoOverride && $(this).is('a[target]')); 143 }).attr({target: '_blank'}); 144 145 // Add noopener rel attribute to combat phishing. 146 $(external_links).attr('rel', function (i, val) { 147 // If no rel attribute is present, create one with the value noopener. 148 if (val === null || typeof val === 'undefined') { 149 return 'noopener'; 150 } 151 // Check to see if rel contains noopener. Add what doesn't exist. 152 if (val.indexOf('noopener') > -1) { 153 if (val.indexOf('noopener') === -1) { 154 return val + ' noopener'; 155 } 156 // Noopener exists. Nothing needs to be added. 157 else { 158 return val; 159 } 160 } 161 // Else, append noopener to val. 162 else { 163 return val + ' noopener'; 164 } 165 }); 166 } 167 168 if (drupalSettings.data.extlink.extNofollow) { 169 $(external_links).attr('rel', function (i, val) { 170 // When the link does not have a rel attribute set it to 'nofollow'. 171 if (val === null || typeof val === 'undefined') { 172 return 'nofollow'; 173 } 174 var target = 'nofollow'; 175 // Change the target, if not overriding follow. 176 if (drupalSettings.data.extlink.extFollowNoOverride) { 177 target = 'follow'; 178 } 179 if (val.indexOf(target) === -1) { 180 return val + ' nofollow'; 181 } 182 return val; 183 }); 184 } 185 186 if (drupalSettings.data.extlink.extNoreferrer) { 187 $(external_links).attr('rel', function (i, val) { 188 // When the link does not have a rel attribute set it to 'noreferrer'. 189 if (val === null || typeof val === 'undefined') { 190 return 'noreferrer'; 191 } 192 if (val.indexOf('noreferrer') === -1) { 193 return val + ' noreferrer'; 194 } 195 return val; 196 }); 197 } 198 199 Drupal.extlink = Drupal.extlink || {}; 200 201 // Set up default click function for the external links popup. This should be 202 // overridden by modules wanting to alter the popup. 203 Drupal.extlink.popupClickHandler = Drupal.extlink.popupClickHandler || function () { 204 if (drupalSettings.data.extlink.extAlert) { 205 return confirm(drupalSettings.data.extlink.extAlertText); 206 } 207 }; 208 209 $(external_links).off("click.extlink"); 210 $(external_links).on("click.extlink", function (e) { 211 return Drupal.extlink.popupClickHandler(e, this); 212 }); 213 }; 214 215 /** 216 * Apply a class and a trailing <span> to all links not containing images. 217 * 218 * @param {object[]} links 219 * An array of DOM elements representing the links. 220 * @param {string} class_name 221 * The class to apply to the links. 222 * @param {string} icon_placement 223 * 'append' or 'prepend' the icon to the link. 224 */ 225 Drupal.extlink.applyClassAndSpan = function (links, class_name, icon_placement) { 226 var $links_to_process; 227 if (drupalSettings.data.extlink.extImgClass) { 228 $links_to_process = $(links); 229 } 230 else { 231 var links_with_images = $(links).find('img, svg').parents('a'); 232 $links_to_process = $(links).not(links_with_images); 233 } 234 235 if (class_name !== '0') { 236 $links_to_process.addClass(class_name); 237 } 238 239 // Add data-extlink attribute. 240 $links_to_process.attr('data-extlink', ''); 241 242 var i; 243 var length = $links_to_process.length; 244 for (i = 0; i < length; i++) { 245 var $link = $($links_to_process[i]); 246 if (drupalSettings.data.extlink.extUseFontAwesome) { 247 if (class_name === drupalSettings.data.extlink.mailtoClass) { 248 $link[icon_placement]('<span class="fa-' + class_name + ' extlink">
248<span class="' + drupalSettings.data.extlink.extFaMailtoClasses + '" aria-label="' + drupalSettings.data.extlink.mailtoLabel + '"></span></span>'); 249 } 250 else { 251 $link[icon_placement]('<span class="fa-' + class_name + ' extlink"><span class="' + drupalSettings.data.extlink.extFaLinkClasses + '" aria-label="' + drupalSettings.data.extlink.extLabel + '"></span></span>'); 252 } 253 } 254 else { 255 if (class_name === drupalSettings.data.extlink.mailtoClass) { 256 $link[icon_placement]('<svg focusable="false" class="' + class_name + '" role="img" aria-label="' + drupalSettings.data.extlink.mailtoLabel + '" xmlns="http://www.w3.org/2000/svg" viewBox="0 10 70 20"><metadata><sfw xmlns="http://ns.adobe.com/SaveForWeb/1.0/"><sliceSourceBounds y="-8160" x="-8165" width="16389" height="16384" bottomLeftOrigin="true"/><optimizationSettings><targetSettings targetSettingsID="0" fileFormat="PNG24Format"><PNG24Format transparency="true" filtered="false" interlaced="false" noMatteColor="false" matteColor="#FFFFFF"/></targetSettings></optimizationSettings></sfw></metadata><title>' + drupalSettings.data.extlink.mailtoLabel + '</title><path d="M56 14H8c-1.1 0-2 0.9-2 2v32c0 1.1 0.9 2 2 2h48c1.1 0 2-0.9 2-2V16C58 14.9 57.1 14 56 14zM50.5 18L32 33.4 13.5 18H50.5zM10 46V20.3l20.7 17.3C31.1 37.8 31.5 38 32 38s0.9-0.2 1.3-0.5L54 20.3V46H10z"/></svg>'); 257 } 258 else { 259 $link[icon_placement]('<svg focusable="false" class="' + class_name + '" role="img" aria-label="' + drupalSettings.data.extlink.extLabel + '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 40"><metadata><sfw xmlns="http://ns.adobe.com/SaveForWeb/1.0/"><sliceSourceBounds y="-8160" x="-8165" width="16389" height="16384" bottomLeftOrigin="true"/><optimizationSettings><targetSettings targetSettingsID="0" fileFormat="PNG24Format"><PNG24Format transparency="true" filtered="false" interlaced="false" noMatteColor="false" matteColor="#FFFFFF"/></targetSettings></optimizationSettings></sfw></metadata><title>' + drupalSettings.data.extlink.extLabel + '</title><path d="M48 26c-1.1 0-2 0.9-2 2v26H10V18h26c1.1 0 2-0.9 2-2s-0.9-2-2-2H8c-1.1 0-2 0.9-2 2v40c0 1.1 0.9 2 2 2h40c1.1 0 2-0.9 2-2V28C50 26.9 49.1 26 48 26z"/><path d="M56 6H44c-1.1 0-2 0.9-2 2s0.9 2 2 2h7.2L30.6 30.6c-0.8 0.8-0.8 2 0 2.8C31 33.8 31.5 34 32 34s1-0.2 1.4-0.6L54 12.8V20c0 1.1 0.9 2 2 2s2-0.9 2-2V8C58 6.9 57.1 6 56 6z"/></svg>'); 260 } 261 } 262 } 263 }; 264 265 Drupal.behaviors.extlink = Drupal.behaviors.extlink || {}; 266 Drupal.behaviors.extlink.attach = function (context, drupalSettings) { 267 // Backwards compatibility, for the benefit of modules overriding extlink 268 // functionality by defining an "extlinkAttach" global function. 269 if (typeof extlinkAttach === 'function') { 270 extlinkAttach(context); 271 } 272 else { 273 Drupal.extlink.attach(context, drupalSettings); 274 } 275 }; 276 277})(jQuery, Drupal, drupalSettings);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.