1/** 2 * replace external link target and rel attributes 3 * to open external links in a new tab 4 * @param {string} selector css selector for all links to handle 5 */ 6function handleExternalLinks(selector) { 7 selector = selector ?? `a[href]:not([href*="${window.location.hostname}"]):not([href^="javascript"])` 8 const links = document.querySelectorAll(selector); 9 links.forEach(link => { 10 if (link.hostname !== window.location.hostname) { 11 if (!link.target) { 12 link.target = '_blank'; 13 } 14 if (!link.hasAttribute("rel")) { 15 link.setAttribute('rel', 'noreferrer'); 16 } 17 } 18 }); 19} 20 21/** 22 * body class handling for no-js and touch devices 23 */ 24function applyBodyClasses() { 25 const body = document.body; 26 body.classList.remove('no-js'); 27 body.classList.add('javascript'); 28 if ('ontouchstart' in document.documentElement) body.classList.add('is-touch'); 29} 30 31/** 32 * create email links from data-email placeholders 33 */ 34function createEmailLinks() { 35 const mailLinks = document.querySelectorAll('a[data-email]'); 36 mailLinks.forEach(link => { 37 const data = JSON.parse(link.getAttribute('data-email')), 38 escape = link.querySelector('span.escape'); 39 link.href = `mailto:${data.name}@${data.host}` 40 if (escape) escape.outerHTML = '@'; 41 }); 42} 43 44/** 45 * check if a current css breakpoint of viewport width is matching 46 * @param {string} width name of the breakpoint 47 * @param {string} direction direction, can be either "min", or "max" 48 * @returns boolean 49 */ 50function breakpoint(width, direction) { 51 const breakpoints = new Array(); 52 for (bp in cssData['gridMap']) { 53 if (cssData['gridMap'][bp].width === '$rowMaxWidth') breakpoints[bp] = parseInt(cssData['rowMaxWidth'].replace('px', '')) / 16 + 'em'; 54 else breakpoints[bp] = parseInt(cssData['gridMap'][bp].width.replace('px', '')) / 16 + 'em'; 55 } 56 direction = (typeof direction === 'undefined') ? 'min' : direction; 57 if (typeof width === 'undefined') { 58 return breakpoints 59 } else { 60 if (direction === 'min') return window.matchMedia("(min-width: " + breakpoints[width] + ")").matches; 61 else return window.matchMedia("(max-width: " + breakpoints[width].replace(/(\d|\W)*/, function (m) { return parseFloat(m) - 0.0625 }) + ")").matches 62 } 63} 64 65/** 66 * returns the current css breakpoints name as a string, e.g. "large" 67 * @author Dominik Kressler 68 * @thanks Eric Holtkamp 69 * @date 2020-05-05 70 * 71 */ 72function currentBp() { 73 const bp = breakpoint(); 74 const checkWidth = (window.innerWidth + 1) / parseInt(cssData['base-font-size']); 75 const values = Object.values(bp).map(function (x) { return parseFloat(x); }); 76 values.sort(function (a, b) { return a - b; }); 77 let result = false;
78 79 for (let i = 0, c = values.length; i < c; i++) { 80 if (i < values.length - 1 && checkWidth >= values[i] && checkWidth <= values[(i + 1)]) { 81 result = values[i]; 82 break; 83 } 84 } 85 if (result === false) result = values[values.length - 1]; 86 87 return Object.keys(bp)[Object.values(bp).indexOf(result + 'em')]; 88} 89 90/** 91 * add direction of scrolling as body class 92 * @param {HTMLElement} element the element to apply classes to 93 */ 94function addScrollDirection(element) { 95 let lastScrollTop = 0; 96 document.addEventListener('scroll', function (e) { 97 const scrollTop = window.scrollY || document.documentElement.scrollTop; 98 if (scrollTop > lastScrollTop) { 99 element.classList.add('scrolling-down'); 100 element.classList.remove('scrolling-up'); 101 } 102 else if (scrollTop != lastScrollTop) { 103 element.classList.remove('scrolling-down'); 104 element.classList.add('scrolling-up'); 105 } 106 lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; 107 }); 108} 109 110/** 111 * allow clicking elements with a matching tab index by pressing "Enter" 112 */ 113function handleEnterKeyClick() { 114 window.addEventListener('keydown', function (e) { 115 if (e.key === "Enter" && document.activeElement.getAttribute('tabindex') != null) { 116 document.activeElement.click(); 117 } 118 }); 119} 120 121/** 122 * add class to element if scroll detection injection 123 * @param {string or number} scrollDetect detect a element or fix number for init 124 * @param {string} scrollClassName name of scroll class for element 125 * @param {HTMLElement} element the element to apply classes to 126 */ 127function addScrollClass(scrollDetect = 100, scrollClassName = 'scrolled', element = document.body) { 128 if (typeof scrollDetect === "string") { 129 scrollDetect = document.querySelector(scrollDetect).getBoundingClientRect().height 130 } 131 132 function init() { 133 const scrollTop = window.scrollY || document.documentElement.scrollTop; 134 if (scrollTop >= scrollDetect) { 135 element.classList.add(scrollClassName) 136 } 137 else { 138 element.classList.remove(scrollClassName) 139 } 140 } 141 142 init() 143 144 window.addEventListener('scroll', () => { 145 init() 146 }, false) 147} 148 149/** 150 * load fonts from theme css variables 151 * this will create font-face declarations with new font-family names 152 * to overwrite existing ones, and set the theme variables from 153 * the template as actual variables once the fonts loaded 154 */ 155async function loadFonts() { 156 157 /** 158 * suffix which gets appended to font family, 159 * needed to overwrite previous font face declarations, 160 * because font face declarations for the same font family 161 * do not work reliably 162 */ 163 const suffix = "theme"; 164 165 /** 166 * wrapper for theme cms theme variables, 167 * used for observation to detect changes to font variables 168 */ 169 const themeWrapper = document.querySelector("head"); 170 171 /** 172 * setup mutation observer to listen for changes 173 * to the font configuration 174 */ 175 const setUpObserver = () => { 176 const observer = new MutationObserver(() => { 177 console.log("font config change detected"); 178 updateFonts(); 179 }); 180 observer.observe(themeWrapper, { 181 childList: true, 182 subtree: true, 183 }); 184 }; 185 186 /** 187 * get theme variable style tag 188 * @returns {HTMLStyleElement|undefined} 189 */ 190 const getThemeVariableStyle = () => { 191 const themeFontsTemplate = document.querySelector("#cms-theme-fonts"); 192 if (!themeFontsTemplate) return; 193 const themeFontsDoc = themeFontsTemplate.content.cloneNode(true); 194 const themeStyle = themeFontsDoc.querySelector("style"); 195 return themeStyle; 196 }; 197 198 /** 199 * parse css stylesheet from theme variable style tag 200 * @param {HTMLStyleElement} styleEl 201 * @returns {[CSSStyleDeclaration|undefined, CSSStyleSheet]} 202 */ 203 const parseThemeVariables = (styleEl) => { 204 const sheet = new CSSStyleSheet(); 205 sheet.replaceSync(styleEl.innerHTML); 206 const style = sheet.cssRules[0].style; 207 return [style, sheet]; 208 }; 209 210 /** 211 * create font face declarations from cms theme fonts template 212 * @param {object} globalFontVariables 213 * @param {CSSStyleDeclaration} themeFontVariables 214 * @returns {FontFace[]} 215 */ 216 const createFontFaces = (globalFontVariables, themeFontVariables) => { 217 const fonts = []; 218 // create all font files and associate them 219 // with the themes actual font names and weights 220 for (const fontKey in globalFontVariables) { 221 const fontName = themeFontVariables.getPropertyValue(`--font-${fontKey}`); 222 const escapedFontName = fontName.replaceAll("\"", ""); 223 const randomizedName = `${escapedFontName}-${suffix}`; 224 // update font name var 225 themeFontVariables.setProperty(`--font-${fontKey}`, `"${randomizedName}"`); 226 227 // get font weights 228 for (const weightKey in globalFontVariables[fontKey].weights) { 229 const fontWeight = themeFontVariables.getPropertyValue(`--font-${fontKey}-${weightKey}`); 230 // remove surrounding quotation marks for defining FontFace 231 // define font face for each weight 232 fonts.push( 233 new FontFace(randomizedName, `url(/fonts/font-${fontKey}-${weightKey}.woff2)`, { 234 weight: fontWeight, 235 }) 236 ); 237 } 238 } 239 return fonts; 240 }; 241 242 /** 243 * initiates loading all font faces in parallel, 244 * resolves when all fonts loaded 245 * @param {FontFace[]} fonts 246 * @returns {Promise<void>} 247 */ 248 const loadAllFontFaces = async (fonts) => { 249 // return Promise.allSettled(fonts.map(font => font.load())) 250 return Promise.all(fonts.map((font) => font.load())); 251 }; 252 253 /** 254 * replaces all existing font face declarations and their corresponding theme variables 255 * in the document with new ones 256 * @param {FontFace[]} fonts 257 * @param {CSSStyleSheet} style 258 * @param {string[]} fontsToDelete array of previously added font names (font family), 259 * which will be removed when font declaration updates 260 */ 261 const replaceFontsInDocument = (fonts, style, fontsToDelete) => { 262 // add loaded fonts to the document
263 fonts.forEach((font) => document.fonts.add(font)); 264 265 // remove previous font declarations 266 fontsToDelete.map((font) => document.fonts.delete(font)); 267 268 // add style tag with css variables containing font names 269 // to the dom, remove any old ones 270 const loadedFonts = document.querySelector("#cms-theme-fonts-loaded"); 271 if (loadedFonts) loadedFonts.remove(); 272 document.adoptedStyleSheets.push(style); 273 window.WizardMessageBroker && window.WizardMessageBroker.fireEvent("cms-theme-fonts-loaded", {}); 274 window.dispatchEvent(new CustomEvent("cms-theme-fonts-loaded")); 275 }; 276 277 /** 278 * keep track of previous added fonts, which should be removed on a font update, 279 */ 280 let oldFonts = []; 281 282 /** 283 * update all fonts with new values 284 */ 285 const updateFonts = async () => { 286 try { 287 const style = getThemeVariableStyle(); 288 if (!style) { 289 console.log("No theme font snippet found"); 290 return; 291 } 292 const [themeFontVariables, sheet] = parseThemeVariables(style); 293 const globalFontVariables = window.cssData["fontMap"]; 294 const fonts = createFontFaces(globalFontVariables, themeFontVariables); 295 await loadAllFontFaces(fonts); 296 replaceFontsInDocument(fonts, sheet, oldFonts); 297 oldFonts = fonts; 298 } catch (e) { 299 console.error(`Font file could not be loaded`, e); 300 } 301 }; 302 303 //setUpObserver(); 304 return await updateFonts(); 305} 306 307 308/** 309 * summarize all basics functions 310 */ 311function basics() { 312 loadFonts(); 313 applyBodyClasses(); 314 createEmailLinks(); 315 handleExternalLinks(); 316 addScrollDirection(document.body); 317 handleEnterKeyClick(); 318}
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.