1(function (Drupal, once) { 2 'use strict'; 3 4 Drupal.behaviors.osggQuicktabsDeepLink = { 5 attach(context) { 6 const url = new URL(window.location.href); 7 8 once('osgg-quicktabs-deep-link', 'div.quicktabs-wrapper[id^="quicktabs-"]', context).forEach((wrapper) => { 9 const instanceId = getInstanceId(wrapper); 10 const tabs = getTabs(wrapper); 11 if (!instanceId || !tabs.length) { 12 return; 13 } 14 15 const targetIndex = getTargetIndex(url, instanceId, tabs); 16 if (targetIndex === null || !tabs[targetIndex]) { 17 return; 18 } 19 20 setActiveTabCookie(instanceId, targetIndex); 21 22 // Defer the click until other behaviors have finished attaching. 23 window.setTimeout(() => { 24 if (wrapper.isConnected) { 25 tabs[targetIndex].click(); 26 } 27 }, 0); 28 }); 29 } 30 }; 31 32 function getInstanceId(wrapper) { 33 return wrapper.id.startsWith('quicktabs-') ? wrapper.id.substring('quicktabs-'.length) : ''; 34 } 35 36 function getTabs(wrapper) { 37 return Array.from(wrapper.querySelectorAll('ul.quicktabs-tabs li a')); 38 } 39 40 function getTargetIndex(url, instanceId, tabs) { 41 const explicitValue = getExplicitValue(url, instanceId); 42 if (explicitValue !== null) { 43 return resolveExplicitValue(explicitValue, tabs); 44 } 45 46 const legacyValue = getLegacyValue(url, instanceId); 47 if (legacyValue !== null) { 48 return resolveLegacyValue(legacyValue, tabs); 49 } 50 51 return getHashIndex(url.hash, instanceId, tabs); 52 } 53 54 function getExplicitValue(url, instanceId) { 55 const candidates = [ 56 `qt-${instanceId}`, 57 `qt_${instanceId}` 58 ]; 59 60 for (const candidate of candidates) { 61 const value = url.searchParams.get(candidate); 62 if (value !== null && value !== '') { 63 return value; 64 } 65 } 66 67 return null; 68 } 69 70 function getLegacyValue(url, instanceId) { 71 const normalizedInstanceId = normalizeKey(instanceId); 72 73 for (const [key, value] of url.searchParams.entries()) { 74 if (!value || !/^qt[-_]/i.test(key)) { 75 continue; 76 } 77 78 const normalizedKey = normalizeKey(key); 79 if (normalizedKey.includes(normalizedInstanceId)) { 80 return value; 81 } 82 } 83 84 return null; 85 } 86 87 function resolveExplicitValue(value, tabs) { 88 const trimmedValue = value.trim(); 89 if (/^\d+$/.test(trimmedValue)) { 90 const numericValue = parseInt(trimmedValue, 10); 91 const index = numericValue === 0 ? 0 : numericValue - 1; 92 return index >= 0 && index < tabs.length ? index : null; 93 } 94 95 return resolveSlug(trimmedValue, tabs); 96 } 97 98 function resolveLegacyValue(value, tabs) { 99 const trimmedValue = value.trim(); 100 if (/^\d+$/.test(trimmedValue)) { 101 const index = parseInt(trimmedValue, 10); 102 return index >= 0 && index < tabs.length ? index : null; 103 } 104 105 return resolveSlug(trimmedValue, tabs); 106 } 107 108 function getHashIndex(hash, instanceId, tabs) { 109 const decodedHash = decodeURIComponent(hash.replace(/^#/, '')); 110 const patterns = [ 111 new RegExp(`^quicktabs-tab(?:page)?-${escapeRegExp(instanceId)}-(\\d+)$`, 'i'), 112 new RegExp(`^qt[-_]${escapeRegExp(instanceId)}[-_](\\d+)$`, 'i') 113 ]; 114 115 for (const pattern of patterns) { 116 const match = decodedHash.match(pattern); 117 if (!match) { 118 continue; 119 } 120 121 const index = parseInt(match[1], 10); 122 if (index >= 0 && index < tabs.length) { 123 return index; 124 } 125 } 126 127 return null; 128 } 129 130 function resolveSlug(value, tabs) { 131 const normalizedValue = slugify(value); 132 if (!normalizedValue) { 133 return null; 134 } 135 136 return tabs.findIndex((tab) => slugify(tab.textContent) === normalizedValue); 137 } 138 139 function setActiveTabCookie(instanceId, index) { 140 document.cookie = [ 141 `Drupal-quicktabs-active-tab-id-${instanceId}=${index}`, 142 'path=/', 143 'SameSite=Lax' 144 ].join('; '); 145 } 146 147 function normalizeKey(value) { 148 return value.toLowerCase().replace(/[^a-z0-9]+/g, '_'); 149 } 150 151 function slugify(value) { 152 return value 153 .normalize('NFD') 154 .replace(/[\u0300-\u036f]/g, '') 155 .toLowerCase() 156 .replace(/[^a-z0-9]+/g, '-') 157 .replace(/^-+|-+$/g, ''); 158 } 159 160 function escapeRegExp(value) { 161 return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 162 } 163 164})(Drupal, once);
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.