Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
extlink
Manage
Activity
Members
Labels
Plan
Wiki
Custom issue tracker
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Model registry
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
project
extlink
Commits
3497129c
Commit
3497129c
authored
1 year ago
by
Stephen Mustgrave
Browse files
Options
Downloads
Patches
Plain Diff
Issue
#3238995
by solideogloria, smustgrave, granik, tyler36: Remove jQuery dependency
parent
7ca93412
Branches
Branches containing commit
Tags
Tags containing commit
3 merge requests
!38
Issue #3468454
,
!37
Issue #3468454
,
!32
Issue #3199408 by mparker17, smustgrave, Wim Leers, Anybody: Migrate...
Pipeline
#210834
passed with warnings
1 year ago
Stage: build
Stage: validate
Stage: test
Changes
4
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
.cspell-project-words.txt
+1
-0
1 addition, 0 deletions
.cspell-project-words.txt
.gitignore
+8
-0
8 additions, 0 deletions
.gitignore
extlink.libraries.yml
+0
-1
0 additions, 1 deletion
extlink.libraries.yml
js/extlink.js
+104
-93
104 additions, 93 deletions
js/extlink.js
with
113 additions
and
94 deletions
.cspell-project-words.txt
+
1
−
0
View file @
3497129c
...
...
@@ -2,6 +2,7 @@ Canil
Drupaluser
elachlan
exlink
extlinks
Haug
Lachlan
Mustgrave
...
...
This diff is collapsed.
Click to expand it.
.gitignore
+
8
−
0
View file @
3497129c
node_modules/*
yarn.lock
# Exclude development files.
.ddev/
.vscode/
/vendor/
/web/
.editorconfig
.gitattributes
phpcs.xml.dist
This diff is collapsed.
Click to expand it.
extlink.libraries.yml
+
0
−
1
View file @
3497129c
...
...
@@ -6,7 +6,6 @@ drupal.extlink:
theme
:
css/extlink.css
:
{}
dependencies
:
-
core/jquery
-
core/drupal
-
core/drupalSettings
...
...
This diff is collapsed.
Click to expand it.
js/extlink.js
+
104
−
93
View file @
3497129c
...
...
@@ -2,15 +2,16 @@
* @file
* External links js file.
*/
(
function
(
$
,
Drupal
,
drupalSettings
)
{
((
Drupal
,
drupalSettings
)
=>
{
Drupal
.
extlink
=
Drupal
.
extlink
||
{};
Drupal
.
extlink
.
attach
=
function
(
context
,
drupalSettings
)
{
Drupal
.
extlink
.
attach
=
(
context
,
drupalSettings
)
=>
{
if
(
typeof
drupalSettings
.
data
===
'
undefined
'
||
!
drupalSettings
.
data
.
hasOwnProperty
(
'
extlink
'
))
{
return
;
}
// Define the
jQuery
method (either 'append' or 'prepend') of placing the
// Define the method (either 'append' or 'prepend') of placing the
// icon, defaults to 'append'.
let
extIconPlacement
=
'
append
'
;
if
(
drupalSettings
.
data
.
extlink
.
extIconPlacement
&&
drupalSettings
.
data
.
extlink
.
extIconPlacement
!==
'
0
'
)
{
...
...
@@ -72,27 +73,25 @@
// to ftp://, javascript:, etc. other kinds of links.
// When operating on the 'this' variable, the host has been appended to
// all links by the browser, even local ones.
// In jQuery 1.1 and higher, we'd use a filter method here, but it is not
// available in jQuery 1.0 (Drupal 5 default).
const
externalLinks
=
[];
let
externalLinks
=
[];
const
mailtoLinks
=
[];
$
(
'
a:not([data-extlink]), area:not([data-extlink])
'
,
context
).
each
(
function
(
el
)
{
const
extlinks
=
context
.
querySelectorAll
(
'
a:not([data-extlink]), area:not([data-extlink])
'
);
extlinks
.
forEach
((
el
)
=>
{
try
{
let
url
=
''
;
if
(
typeof
this
.
href
===
'
string
'
)
{
url
=
this
.
href
.
toLowerCase
();
if
(
typeof
el
.
href
===
'
string
'
)
{
url
=
el
.
href
.
toLowerCase
();
}
// Handle SVG links (xlink:href).
else
if
(
typeof
this
.
href
===
'
object
'
)
{
url
=
this
.
href
.
baseVal
;
else
if
(
typeof
el
.
href
===
'
object
'
)
{
url
=
el
.
href
.
baseVal
;
}
if
(
url
.
indexOf
(
'
http
'
)
===
0
&&
((
!
internalLink
.
test
(
url
)
&&
!
(
extExclude
&&
extExclude
.
test
(
url
)))
||
(
extInclude
&&
extInclude
.
test
(
url
)))
&&
// eslint-disable-next-line jquery/no-is
!
(
extCssExclude
&&
$
(
this
).
is
(
extCssExclude
))
&&
!
(
extCssExclude
&&
$
(
this
).
parents
(
extCssExclude
).
length
>
0
)
&&
!
(
extCssExplicit
&&
$
(
this
).
parents
(
extCssExplicit
).
length
<
1
)
!
(
extCssExclude
&&
el
.
matches
(
extCssExclude
))
&&
!
(
extCssExclude
&&
el
.
closest
(
extCssExclude
))
&&
!
(
extCssExplicit
&&
!
el
.
closest
(
extCssExplicit
))
)
{
let
match
=
false
;
if
(
whitelistedDomains
)
{
...
...
@@ -104,20 +103,13 @@
}
}
if
(
!
match
)
{
externalLinks
.
push
(
this
);
externalLinks
.
push
(
el
);
}
}
// Do not include area tags with begin with mailto: (this prohibits
// icons from being added to image-maps).
else
if
(
this
.
tagName
!==
'
AREA
'
&&
url
.
indexOf
(
'
mailto:
'
)
===
0
&&
// eslint-disable-next-line jquery/no-is
!
(
extCssExclude
&&
$
(
this
).
is
(
extCssExclude
))
&&
!
(
extCssExclude
&&
$
(
this
).
parents
(
extCssExclude
).
length
>
0
)
&&
!
(
extCssExplicit
&&
$
(
this
).
parents
(
extCssExplicit
).
length
<
1
)
)
{
mailtoLinks
.
push
(
this
);
else
if
(
el
.
tagName
!==
'
AREA
'
&&
url
.
indexOf
(
'
mailto:
'
)
===
0
&&
!
(
extCssExclude
&&
el
.
closest
(
extCssExclude
))
&&
!
(
extCssExplicit
&&
!
el
.
closest
(
extCssExplicit
)))
{
mailtoLinks
.
push
(
el
);
}
}
catch
(
error
)
{
// IE7 throws errors often when dealing with irregular links, such as:
...
...
@@ -136,41 +128,46 @@
}
if
(
drupalSettings
.
data
.
extlink
.
extTarget
)
{
// Apply the target attribute to all links.
$
(
externalLinks
)
.
filter
(
function
()
{
// Filter out links with target set if option specified.
// eslint-disable-next-line jquery/no-is
return
!
(
drupalSettings
.
data
.
extlink
.
extTargetNoOverride
&&
$
(
this
).
is
(
'
a[target]
'
));
})
.
attr
({
target
:
'
_blank
'
});
// Add target attr to open link in a new tab if not set.
externalLinks
.
forEach
((
link
,
i
)
=>
{
if
(
!
(
drupalSettings
.
data
.
extlink
.
extTargetNoOverride
&&
link
.
matches
(
'
a[target]
'
)))
{
externalLinks
[
i
].
setAttribute
(
'
target
'
,
'
_blank
'
);
}
});
// Add noopener rel attribute to combat phishing.
$
(
externalLinks
).
attr
(
'
rel
'
,
function
(
i
,
val
)
{
externalLinks
.
forEach
((
link
,
i
)
=>
{
const
val
=
link
.
getAttribute
(
'
rel
'
);
// If no rel attribute is present, create one with the value noopener.
if
(
val
===
null
||
typeof
val
===
'
undefined
'
)
{
return
'
noopener
'
;
externalLinks
[
i
].
setAttribute
(
'
rel
'
,
'
noopener
'
);
return
;
}
// Check to see if rel contains noopener. Add what doesn't exist.
if
(
val
.
indexOf
(
'
noopener
'
)
>
-
1
)
{
if
(
val
.
indexOf
(
'
noopener
'
)
===
-
1
)
{
return
`
${
val
}
noopener`
;
// Add noopener.
externalLinks
[
i
].
setAttribute
(
'
rel
'
,
`
${
val
}
noopener`
);
}
else
{
// Noopener exists. Nothing needs to be added.
}
// Noopener exists. Nothing needs to be added.
return
val
;
}
// Else, append noopener to val.
return
`
${
val
}
noopener`
;
else
{
// Add noopener.
externalLinks
[
i
].
setAttribute
(
'
rel
'
,
`
${
val
}
noopener`
);
}
});
}
if
(
drupalSettings
.
data
.
extlink
.
extNofollow
)
{
$
(
externalLinks
).
attr
(
'
rel
'
,
function
(
i
,
val
)
{
externalLinks
.
forEach
((
link
,
i
)
=>
{
const
val
=
link
.
getAttribute
(
'
rel
'
);
// When the link does not have a rel attribute set it to 'nofollow'.
if
(
val
===
null
||
typeof
val
===
'
undefined
'
)
{
return
'
nofollow
'
;
externalLinks
[
i
].
setAttribute
(
'
rel
'
,
'
nofollow
'
);
return
;
}
let
target
=
'
nofollow
'
;
// Change the target, if not overriding follow.
...
...
@@ -178,41 +175,47 @@
target
=
'
follow
'
;
}
if
(
val
.
indexOf
(
target
)
===
-
1
)
{
return
`
${
val
}
nofollow`
;
// Add nofollow.
externalLinks
[
i
].
setAttribute
(
'
rel
'
,
`
${
val
}
nofollow`
);
}
return
val
;
});
}
if
(
drupalSettings
.
data
.
extlink
.
extNoreferrer
)
{
$
(
externalLinks
).
attr
(
'
rel
'
,
function
(
i
,
val
)
{
externalLinks
.
forEach
((
link
,
i
)
=>
{
const
val
=
link
.
getAttribute
(
'
rel
'
);
// When the link does not have a rel attribute set it to 'noreferrer'.
if
(
val
===
null
||
typeof
val
===
'
undefined
'
)
{
return
'
noreferrer
'
;
externalLinks
[
i
].
setAttribute
(
'
rel
'
,
'
noreferrer
'
);
return
;
}
if
(
val
.
indexOf
(
'
noreferrer
'
)
===
-
1
)
{
return
`
${
val
}
noreferrer`
;
}
return
val
;
// Add nofollow.
externalLinks
[
i
].
setAttribute
(
'
rel
'
,
`
${
val
}
noreferrer`
);
});
}
/* eslint:disable:no-empty */
Drupal
.
extlink
=
Drupal
.
extlink
||
{};
// Set up default click function for the external links popup. This should be
// overridden by modules wanting to alter the popup.
Drupal
.
extlink
.
popupClickHandler
=
Drupal
.
extlink
.
popupClickHandler
||
function
()
{
(()
=>
{
if
(
drupalSettings
.
data
.
extlink
.
extAlert
)
{
// eslint-disable-next-line no-restricted-globals
return
confirm
(
drupalSettings
.
data
.
extlink
.
extAlertText
);
}
};
}
)
;
$
(
externalLinks
).
off
(
'
click.extlink
'
);
$
(
externalLinks
).
on
(
'
click.extlink
'
,
function
(
e
)
{
return
Drupal
.
extlink
.
popupClickHandler
(
e
,
this
);
const
_that
=
this
;
externalLinks
.
forEach
((
val
,
i
)
=>
{
externalLinks
[
i
].
removeEventListener
(
'
click
'
,
()
=>
{
return
Drupal
.
extlink
.
popupClickHandler
.
call
(
_that
);
});
externalLinks
[
i
].
addEventListener
(
'
click
'
,
()
=>
{
return
Drupal
.
extlink
.
popupClickHandler
.
call
(
_that
);
});
});
};
...
...
@@ -226,59 +229,67 @@
* @param {string} iconPlacement
* 'append' or 'prepend' the icon to the link.
*/
Drupal
.
extlink
.
applyClassAndSpan
=
function
(
links
,
className
,
iconPlacement
)
{
let
$
linksToProcess
;
Drupal
.
extlink
.
applyClassAndSpan
=
(
links
,
className
,
iconPlacement
)
=>
{
let
linksToProcess
;
if
(
drupalSettings
.
data
.
extlink
.
extImgClass
)
{
$
linksToProcess
=
$
(
links
)
;
linksToProcess
=
links
;
}
else
{
const
linksWithImages
=
$
(
links
).
find
(
'
img, svg
'
).
parents
(
'
a
'
);
$linksToProcess
=
$
(
links
).
not
(
linksWithImages
);
// Only text links.
linksToProcess
=
links
.
filter
((
link
)
=>
{
return
link
.
querySelector
(
'
img, svg
'
)
===
null
;
});
}
if
(
className
!==
'
0
'
)
{
$linksToProcess
.
addClass
(
className
);
}
for
(
let
i
=
0
;
i
<
linksToProcess
.
length
;
i
++
)
{
if
(
className
!==
'
0
'
)
{
linksToProcess
[
i
].
classList
.
add
(
className
);
}
// Additional classes:
if
(
className
===
drupalSettings
.
data
.
extlink
.
mailtoClass
&&
drupalSettings
.
data
.
extlink
.
extAdditionalMailtoClasses
)
{
// Is mail link:
$
linksToProcess
.
addClass
(
drupalSettings
.
data
.
extlink
.
extAdditionalMailtoClasses
);
}
else
if
(
drupalSettings
.
data
.
extlink
.
extAdditionalLinkClasses
)
{
// Is regular external link:
$
linksToProcess
.
addClass
(
drupalSettings
.
data
.
extlink
.
extAdditionalLinkClasses
);
}
// Additional classes:
if
(
className
===
drupalSettings
.
data
.
extlink
.
mailtoClass
&&
drupalSettings
.
data
.
extlink
.
extAdditionalMailtoClasses
)
{
// Is mail link:
linksToProcess
[
i
].
classList
.
add
(
drupalSettings
.
data
.
extlink
.
extAdditionalMailtoClasses
);
}
else
if
(
drupalSettings
.
data
.
extlink
.
extAdditionalLinkClasses
)
{
// Is regular external link:
linksToProcess
[
i
].
classList
.
add
(
drupalSettings
.
data
.
extlink
.
extAdditionalLinkClasses
);
}
// Add data-extlink attribute.
$
linksToProcess
.
attr
(
'
data-extlink
'
,
''
);
// Add data-extlink attribute.
linksToProcess
[
i
].
setAttribute
(
'
data-extlink
'
,
''
);
let
i
;
const
length
=
$linksToProcess
.
length
;
for
(
i
=
0
;
i
<
length
;
i
++
)
{
const
$link
=
$
(
$linksToProcess
[
i
]);
const
link
=
linksToProcess
[
i
];
// Create an icon element.
let
iconElement
;
if
(
drupalSettings
.
data
.
extlink
.
extUseFontAwesome
)
{
iconElement
=
document
.
createElement
(
'
span
'
);
iconElement
.
setAttribute
(
'
class
'
,
`fa-
${
className
}
extlink`
);
if
(
className
===
drupalSettings
.
data
.
extlink
.
mailtoClass
)
{
$link
[
iconPlacement
](
`<span class="fa-
${
className
}
extlink"><span class="
${
drupalSettings
.
data
.
extlink
.
extFaMailtoClasses
}
" aria-label="
${
drupalSettings
.
data
.
extlink
.
mailtoLabel
}
"></span></span>`
,
);
iconElement
.
innerHTML
=
`<span class="
${
drupalSettings
.
data
.
extlink
.
extFaMailtoClasses
}
" aria-label="
${
drupalSettings
.
data
.
extlink
.
mailtoLabel
}
"></span>`
;
}
else
{
$link
[
iconPlacement
](
`<span class="fa-
${
className
}
extlink"><span class="
${
drupalSettings
.
data
.
extlink
.
extFaLinkClasses
}
" aria-label="
${
drupalSettings
.
data
.
extlink
.
extLabel
}
"></span></span>`
,
);
iconElement
.
innerHTML
=
`<span class="
${
drupalSettings
.
data
.
extlink
.
extFaLinkClasses
}
" aria-label="
${
drupalSettings
.
data
.
extlink
.
extLabel
}
"></span>`
;
}
}
else
if
(
className
===
drupalSettings
.
data
.
extlink
.
mailtoClass
)
{
$link
[
iconPlacement
](
`<svg focusable="false" class="
${
className
}
" role="img" aria-label="
${
drupalSettings
.
data
.
extlink
.
mailtoLabel
}
" aria-hidden="
${
drupalSettings
.
data
.
extlink
.
extHideIcons
}
" 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>`
,
);
}
else
{
$link
[
iconPlacement
](
`<svg focusable="false" class="
${
className
}
" role="img" aria-label="
${
drupalSettings
.
data
.
extlink
.
extLabel
}
" aria-hidden="
${
drupalSettings
.
data
.
extlink
.
extHideIcons
}
" 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>`
,
);
iconElement
=
document
.
createElementNS
(
'
http://www.w3.org/2000/svg
'
,
'
svg
'
);
iconElement
.
setAttribute
(
'
focusable
'
,
'
false
'
);
iconElement
.
classList
.
add
(
className
);
iconElement
.
setAttribute
(
'
role
'
,
'
img
'
);
iconElement
.
setAttribute
(
'
aria-hidden
'
,
drupalSettings
.
data
.
extlink
.
extHideIcons
);
if
(
className
===
drupalSettings
.
data
.
extlink
.
mailtoClass
)
{
iconElement
.
setAttribute
(
'
aria-label
'
,
drupalSettings
.
data
.
extlink
.
mailtoLabel
);
iconElement
.
setAttribute
(
'
viewBox
'
,
'
0 10 70 20
'
);
iconElement
.
innerHTML
=
`<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"/>`
;
}
else
{
iconElement
.
setAttribute
(
'
aria-label
'
,
drupalSettings
.
data
.
extlink
.
extLabel
);
iconElement
.
setAttribute
(
'
viewBox
'
,
'
0 0 80 40
'
);
iconElement
.
innerHTML
=
`<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"/>`
;
}
}
link
[
iconPlacement
](
iconElement
);
}
};
Drupal
.
behaviors
.
extlink
=
Drupal
.
behaviors
.
extlink
||
{};
Drupal
.
behaviors
.
extlink
.
attach
=
function
(
context
,
drupalSettings
)
{
Drupal
.
behaviors
.
extlink
.
attach
=
(
context
,
drupalSettings
)
=>
{
// Backwards compatibility, for the benefit of modules overriding extlink
// functionality by defining an "extlinkAttach" global function.
if
(
typeof
extlinkAttach
===
'
function
'
)
{
...
...
@@ -288,4 +299,4 @@
Drupal
.
extlink
.
attach
(
context
,
drupalSettings
);
}
};
})(
jQuery
,
Drupal
,
drupalSettings
);
})(
Drupal
,
drupalSettings
);
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment