1<!DOCTYPE html> 2<html lang="de"> 3<head> 4 5 <meta charset="utf-8"/> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <meta name="description" content="login_page_seo_meta_tag_description"> 8 9 <title>Grammatip</title> 10 11
11<script src="/javascripts/cookie-helper.js?v=1655670100"></script>
11 12 <link rel="shortcut icon" href="/favicon.ico?v=1782478304" type="image/x-icon" /> 13 14 <!-- PWA Start --> 15<!-- General --> 16<meta data-hid="mobile-web-app-capable" name="mobile-web-app-capable" content="yes"> 17<meta data-hid="og:type" name="og:type" property="og:type" content="website"> 18<meta data-hid="og:title" name="og:title" property="og:title" content="Grammatip"> 19<meta data-hid="og:site_name" name="og:site_name" property="og:site_name" content="Grammatip"> 20<link rel="icon" type="image/x-icon" href="/favicon.ico"> 21<link data-hid="shortcut-icon" rel="shortcut icon" href="/android-chrome-192x192.png"> 22 23<!-- Apple --> 24<meta data-hid="apple-mobile-web-app-title" name="apple-mobile-web-app-title" content="Grammatip"> 25<meta name="theme-color" content="#2D57FC"/> 26<meta name="apple-mobile-web-app-status-bar" content="#2D57FC"/> 27<link data-hid="apple-touch-icon" rel="apple-touch-icon" href="/android-chrome-512x512.png" sizes="512x512"> 28 29<!-- Windows --> 30<meta name="application-name" content="Grammatip"/> 31<meta name="msapplication-TileImage" content="/android-chrome-512x512.png"> 32<meta name="msapplication-TileColor" content="#2D57FC"> 33 34<!-- Manifest --> 35<link rel="manifest" href="/manifest.json" data-hid="manifest" crossorigin="use-credentials"> 36 37<!-- Service worker -->
38<script> 39 40 window.serviceWorkerSupported = true; 41 42 function isIOS16() { 43 return ( 44 navigator.appVersion.includes('Version/16') || 45 navigator.appVersion.includes('OS 16') 46 ) && ( 47 /iPhone/.test(navigator.userAgent) || 48 /iPad/.test(navigator.userAgent) || 49 /iPod/.test(navigator.userAgent) || 50 /Macintosh/.test(navigator.userAgent) 51 ); 52 } 53 54 55 /** 56 * @param tryOnce 57 * @returns {Promise<void|ServiceWorker|*>} 58 * 59 * @see https://stackoverflow.com/a/67612740 60 */ 61 async function registerServiceWorker(tryOnce = false) { 62 if (isIOS16()) { 63 window.serviceWorkerSupported = false; 64 65 try { 66 const reg = await navigator.serviceWorker.getRegistration('/'); 67 await reg.unregister(); 68 } catch (err) { 69 return; 70 } 71 72 return; 73 } 74 75 if (!('serviceWorker' in navigator)) { 76 window.serviceWorkerSupported = false; 77 throw new Error('serviceWorker not supported'); 78 } 79 80 const url = (new URL(`/serviceWorker.js?v=g8534h`, location)).toString(); 81 //console.info('Registering worker'); 82 const registration = await navigator.serviceWorker.register(url, { 83 scope: '/', 84 }); 85 86 const registeredWorker = registration.active || registration.waiting || registration.installing; 87 //console.info('Registered worker:', registeredWorker); 88 if (registeredWorker?.scriptURL != url) { 89 console.log('[ServiceWorker] Old URL:', registeredWorker?.scriptURL || 'none', 'updating to:', url); 90 await registration.update(); 91 //console.info('Updated worker'); 92 } 93 94 //console.info('Waiting for ready worker'); 95 let serviceReg = await navigator.serviceWorker.ready; 96 //console.info('Ready registration:', serviceReg); 97 98 if (!navigator.serviceWorker.controller) { 99 //console.info('Worker isnât controlling, re-register'); 100 try { 101 const reg = await navigator.serviceWorker.getRegistration('/'); 102 //console.info('Unregistering worker'); 103 await reg.unregister(); 104 //console.info('Successfully unregistered, trying registration again'); 105 return registerServiceWorker(); 106 } catch (err) { 107 window.serviceWorkerSupported = false; 108 console.error(`ServiceWorker failed to re-register after hard-refresh, reloading the page!`, err); 109 return location.reload(); 110 } 111 } 112 113 let serviceWorker = serviceReg.active || serviceReg.waiting || serviceReg.installing; 114 if (!serviceWorker) { 115 //console.info('No worker on registration, getting registration again'); 116 serviceReg = await navigator.serviceWorker.getRegistration('/'); 117 serviceWorker = serviceReg.active || serviceReg.waiting || serviceReg.installing; 118 } 119 120 if (!serviceWorker) { 121 //console.info('No worker on registration, waiting 50ms'); 122 await sleep(50); // adjustable or skippable, have a play around 123 } 124 125 serviceWorker = serviceReg.active || serviceReg.waiting || serviceReg.installing; 126 if (!serviceWorker) { 127 window.serviceWorkerSupported = false; 128 throw new Error('after waiting on .ready, still no worker'); 129 } 130 131 if (serviceWorker.state == 'redundant') { 132 //console.info('Worker is redundant, trying again'); 133 return registerServiceWorker(); 134 } 135 136 if (serviceWorker.state != 'activated') { 137 //console.info('Worker IS controlling, but not active yet, waiting on event. state=', serviceWorker.state); 138 try { 139 // timeout is adjustable, but you do want one in case the statechange 140 // doesn't fire / with the wrong state because it gets queued, 141 // see ServiceWorker.onstatechange MDN docs. 142 await timeout(100, new Promise((resolve) => { 143 serviceWorker.addEventListener('statechange', (e) => { 144 if (e.target.state == 'activated') resolve(); 145 }); 146 })); 147 } catch (err) { 148 if (err instanceof TimeoutError) { 149 if (serviceWorker.state != 'activated') { 150 if (tryOnce) { 151 //console.info('Worker is still not active. state=', serviceWorker.state);
152 window.serviceWorkerSupported = false; 153 throw new Error('failed to activate service worker'); 154 } else { 155 //console.info('Worker is still not active, retrying once'); 156 return registerServiceWorker(true); 157 } 158 } 159 } else { 160 // should be unreachable 161 throw err; 162 } 163 } 164 } 165 166 //console.info('Worker is controlling and active, weâre good folks!'); 167 return serviceWorker; 168 } 169 170 class TimeoutError extends Error { 171 } 172 173 /** 174 * Run promise but reject after some timeout. 175 * 176 * @template T 177 * @param {number} ms Milliseconds until timing out 178 * @param {Promise<T>} promise Promise to run until timeout (note that it will keep running after timeout) 179 * @returns {Promise<T, Error>} 180 */ 181 function timeout(ms, promise) { 182 return new Promise((resolve, reject) => { 183 const timer = setTimeout(() => { 184 reject(new TimeoutError); 185 }, ms); 186 187 promise.then((result) => { 188 clearTimeout(timer); 189 resolve(result); 190 }, (error) => { 191 clearTimeout(timer); 192 reject(error); 193 }); 194 }) 195 } 196 197 registerServiceWorker() 198 199</script>
199 200<!-- PWA End --> 201 202 203 <link rel="stylesheet" href="/assets/new/css/web-app.css?id=be3c8f791d151789a2b9627dc861ecbe" type="text/css"> 204 205 206 207
208<script> 209 var analyticsCookies = ['_gcl_au', '_ga', '_gid', '_gat_UA-630585-12']; 210 for (var i in analyticsCookies) { 211 if (!getCookieValue(analyticsCookies[i])) { 212 continue; 213 } 214 215 document.cookie = analyticsCookies[i] + '=; path=/; domain=; expires=Thu, 08 Jan 1970 00:00:01 GMT'; 216 } 217</script>
217 218 219</head> 220<body class=" body-height"> 221 222<div id="vue-app"> 223 <cookies-consent-popup></cookies-consent-popup> 224 225 226 <section class="header-top"> 227 <div class="container py-sm-5 py-3"> 228 <div class="row"> 229 <div class="col-2 col-sm-6"> 230 <a href="/"> 231 <span class="logo"><span class="d-none d-sm-inline text-body">grammatip.de</span></span> 232 </a> 233 </div> 234 <div class="col-10 col-sm-6"> 235 <a href="/" class="btn btn-primary btn-teardrop btn-xl float-end">Zur Startseite</a> 236 </div> 237 </div> 238 </div> 239</section> 240 241 <section class="login-section"> 242 <div class="container py-sm-5 py-3"> 243 <div class="row d-flex justify-content-center align-items-center h-100"> 244 <div class="col col-lg-8 col-xl-6"> 245 <div class="card"> 246 <div class="row g-0"> 247 <div class="col d-flex align-items-center"> 248 <div class="card-body"> 249 <login-form></login-form> 250 </div> 251 </div> 252 </div> 253 </div> 254 </div> 255 </div> 256 257 <div class="row d-flex justify-content-center align-items-center h-100 mt-5"> 258 <div class="col col-lg-6 col-xl-4"> 259 <sso-buttons action-verb="einloggen"></sso-buttons> 260 </div> 261 </div> 262 </div> 263 </section> 264 265 266 <footer class="page-footer"> 267 <div class="container-fluid"> 268 <div class="row flex-row-reverse d-none d-lg-flex"> 269 <div class="col m-0"> 270 <div class="d-flex flex-column flex-md-row justify-content-md-end"> 271 <a class="open-cookie-popup cursor-pointer">Cookie Einstellungen</a> 272 <a href="/policies/privacy">Datenschutz</a> 273 <a href="/grammatip_de_avv.pdf" target="_blank" download>Datenverarbeitungsvertrag (AVV)</a> 274 <a href="/policies/terms">AGB</a> 275 <a href="/policies/impressum">Impressum</a> 276 </div> 277 </div> 278 <div class="col-md-auto m-0 d-flex align-items-end justify-content-end justify-content-md-start"> 279 <div class="d-flex"> 280 <span>Entwickelt von Lemma GmbH</span> 281 </div> 282 </div> 283 </div> 284 </div> 285</footer> 286 </div> 287 288 289
289<script> 290 window.COOKIE_DOMAIN = '.grammatip.de'; 291 window.isPWA = ('' === '1'); 292 </script>
292 293
293<script src="/assets/new/js/manifest.js?id=d4bcf00853dad0a3d96b3597ed8ec186"></script>
293 294
294<script src="/assets/new/js/vendor.js?id=a19c435f8194eff597d954c4ab255344"></script>
294 295
295<script src="/assets/new/js/app.js?id=4377efb2389f3ca32b5da6ebbf1fa628"></script>
295 296
296<script> 297 setInterval(function () { 298 var xmlHttp = new XMLHttpRequest(); 299 xmlHttp.open('GET', '/api/v1/keep-alive?time=' + Date.now(), true); 300 xmlHttp.send(null); 301 }, 300 * 1000); 302</script>
302 303
303<script> 304 // Sets the timeout variable to use later 305 let timeoutId; 306 307 // Listen for the window: online event 308 window.addEventListener('online', async (e) => { 309 timeoutId = setTimeout(loop, 2000); 310 }); 311 312 // Loop function to be called every 2 seconds, via setTimeout 313 async function loop() { 314 // "Ping" the server and wait for response. 315 let status = await ping() 316 if (status === 200) { 317 // Make new notification 318 let notification = { 319 detail: {message: "Internetverbindung ist wieder hergestellt.", type: 'success'}, 320 }; 321 322 // Launch the notification 323 const event = new CustomEvent('add-notification', notification); 324 window.dispatchEvent(event); 325 // Stops the looping 326 clearTimeout(timeoutId) 327 } else { 328 // Run the loop if the "ping" failed 329 timeoutId = setTimeout(loop, 2000); 330 } 331 } 332 333 // "ping" the current website 334 function ping() { 335 let URL = window.location.origin; 336 // Defines the configurations for the request 337 let settings = { 338 url: URL, 339 method: "HEAD", 340 timeout: 5000, 341 // Minimizing the data send 342 maxContentLength: 1, 343 maxBodyLength: 1, 344 transformResponse: [function (data, headers) { 345 // Return empty string, because we are not interested in any data 346 return ""; 347 }], 348 headers: { 349 accept: "application/json", 350 }, 351 }; 352 353 // Sends the request and the returned data is set to only be the status code 354 // Also catches and logs any error that might occur 355 return axios(settings) 356 .then((response) => this.status = response.status) 357 .catch((error) => console.log(error.message)); 358 } 359 360 361 // Listens to offline event and sends a notification 362 window.addEventListener('offline', (e) => { 363 clearTimeout(timeoutId) 364 let notification = { 365 detail: {message: "Es besteht momentan keine Internetverbindung.", type: 'error'}, 366 }; 367 368 const event = new CustomEvent('add-notification', notification); 369 370 window.dispatchEvent(event); 371 }); 372 373</script>
373 374 375 376 <!-- Matomo --> 377
377<script type="text/javascript"> 378 var consentCookie = JSON.parse(getCookieValue('COOKIE_CONSENT')); 379 var siteId = '4'; // Prod site id 380 381 var _paq = window._paq = window._paq || []; 382 if (!consentCookie || consentCookie.statistics != true) { 383 _paq.push(['requireCookieConsent']); // Require cookie consent before adding cookies 384 } 385 386 /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ 387 388 389 _paq.push(['trackPageView']); 390 _paq.push(['enableLinkTracking']); 391 _paq.push(['enableHeartBeatTimer', 30]); 392 (function () { 393 var u = "//analytics.grammatip.com/"; 394 _paq.push(['setTrackerUrl', u + 'matomo.php']); 395 _paq.push(['setSiteId', siteId]); 396 var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0]; 397 g.type = 'text/javascript'; 398 g.async = true; 399 g.src = u + 'matomo.js'; 400 s.parentNode.insertBefore(g, s); 401 })(); 402 </script>
402 403 <!-- End Matomo Code --> 404 405 406
406<script> 407 // enable (bootstrap) tooltips 408 if (!window.isTouchDevice) { 409 var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) 410 var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { 411 return new bootstrap.Tooltip(tooltipTriggerEl); 412 }) 413 } 414 </script>
414 415 416</body> 417</html>
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.