From aae66081531048087f41bd65fce9f2e3a4fafc77 Mon Sep 17 00:00:00 2001 From: Keith Solomon Date: Tue, 11 Nov 2025 11:30:48 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feature:=20Add=20script=20to=20tag=20e?= =?UTF-8?q?xternal=20links=20to=20open=20in=20new=20tab=20and=20add=20clas?= =?UTF-8?q?s=20for=20styling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/js/modules/TagExternalLinks.js | 39 +++++++++++++++++++++++++++ static/js/theme.js | 30 ++++++++++++--------- 2 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 static/js/modules/TagExternalLinks.js 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.`);