diff --git a/static/js/modules/TagExternalLinks.js b/static/js/modules/TagExternalLinks.js new file mode 100644 index 0000000..f341d9c --- /dev/null +++ b/static/js/modules/TagExternalLinks.js @@ -0,0 +1,39 @@ + +/** + * Tags external links in the document with appropriate attributes and styling. + * + * This function identifies all anchor elements with href attributes and determines + * if they point to external domains. External links are enhanced with: + * - Accessibility label indicating they open in a new tab + * - target="_blank" to open in new tab + * - rel="noopener noreferrer" for security + * - Custom CSS class "extLink" for styling + * + * Links are considered external if their host differs from the current page's host. + * Malformed URLs are silently ignored. + * + * @function tagExternalLinks + * @returns {void} + */ + +function tagExternalLinks() { + const currentHost = window.location.host; + + document.querySelectorAll('a[href]').forEach(link => { + try { + const url = new URL(link.href, window.location.href); + + // If the link's host is different from the current host, treat as external + if (url.host !== currentHost) { + link.setAttribute('aria-label', 'External link, opens in a new tab'); + link.setAttribute('target', '_blank'); + link.setAttribute('rel', 'noopener noreferrer'); // Security best practice + link.classList.add('extLink'); // Add a custom class for icons or other styling + } + } catch (e) { + // Ignore malformed URLs + } + }); +} + +export default tagExternalLinks; diff --git a/static/js/theme.js b/static/js/theme.js index 8f29f3c..3d6e871 100644 --- a/static/js/theme.js +++ b/static/js/theme.js @@ -5,6 +5,7 @@ import { registerButtonComponent } from './components/button.js'; import { registerBackToTopButton } from './components/backToTop.js'; import GetHeaderHeight from './modules/GetHeaderHeight.js'; +import tagExternalLinks from './modules/TagExternalLinks.js'; import Navigation from './modules/Navigation.js'; // Add passive event listeners @@ -50,23 +51,26 @@ import Navigation from './modules/Navigation.js'; * Application entrypoint */ document.addEventListener('DOMContentLoaded', () => { - // Register button component - registerButtonComponent(); - registerBackToTopButton(); + // Tag external links + tagExternalLinks(); - // Initialize Navigation - const navigation = new Navigation('navMainToggle', '.menu-vdi__toggle'); + // Register button component + registerButtonComponent(); + registerBackToTopButton(); - // Initialize Navigation - navigation.desktopMenuDropdowns(); - navigation.mobileMenuToggle(); + // Initialize Navigation + const navigation = new Navigation('navMainToggle', '.menu-vdi__toggle'); - // Initialize Header Height - GetHeaderHeight(); + // Initialize Navigation + navigation.desktopMenuDropdowns(); + navigation.mobileMenuToggle(); - // Add Back to Top button to body - const backToTop = document.createElement('back-to-top'); - document.body.appendChild(backToTop); + // Initialize Header Height + GetHeaderHeight(); + + // Add Back to Top button to body + const backToTop = document.createElement('back-to-top'); + document.body.appendChild(backToTop); }); console.log(`theme.js loaded.`);