1/** 2 * Home page (/) JavaScript. 3 * 4 * Extracted from html/index.html as part of the strict CSP rollout. Loads 5 * the latest blog posts via the cpfeed RSS endpoint and lazy-loads 6 * domain_checker.js on window load. 7 * 8 * The URL to lazy-load is read from `<body data-domain-checker-src="...">` 9 * so the script stays free of Twig interpolation. 10 */ 11(function ($) { 12 13 if (!$) { 14 return; 15 } 16 17 $(function () { 18 19 loadBlogPosts(); 20 21 }); 22 23 function loadBlogPosts() { 24 25 $('#blog-feed').empty(); 26 $('#blog-feed').append( 27 '<div class="pt-3 pb-4 w-100 text-center"><i class="fas fa-circle-notch fa-lg fa-spin"></i></div>' 28 ); 29 30 // Apex host: /blog is not proxied on env subdomains (dev.mijn.host / dev.my.host). 31 var objBlog = window.mhBlogLocale ? window.mhBlogLocale() : { host: 'mijn.host', lang: 'nl', homeUrl: 'https://mijn.host/blog/' }; 32 var RSS_URL = 'https://' + objBlog.host + '/blog/feed/?feed=cpfeed&lang=' + encodeURIComponent( objBlog.lang ); 33 var maxArticles = 3; 34 var maxArticlesCounter = 0; 35 var blogsResponseOk = false; 36 37 fetch(RSS_URL) 38 .then(function (response) { 39 blogsResponseOk = !!response.ok; 40 return response.text(); 41 }) 42 .then(function (str) { 43 // Strip style attributes before parse: DOMParser still applies CSP to them. 44 return new window.DOMParser().parseFromString(stripInlineStyles(str), 'text/xml'); 45 }) 46 .then(function (data) { 47 48 if (blogsResponseOk) { 49 50 $('#blog-feed').empty(); 51 52 var items = data.querySelectorAll('item'); 53 var html = ''; 54 55 items.forEach(function (article) { 56 57 maxArticlesCounter++; 58 59 if (maxArticlesCounter <= maxArticles) { 60 html += '<article class="col-sm-6 col-lg-4 blog-item h-auto">' + 61 ' <div class="blog-item-holder">' + 62 ' ' + getThumbnailHtml(article) + 63 ' <div class="blog-posts">' + 64 ' <h3 class="h5">' + getArticleHtml(article, 'title') + '</h3>' + 65 ' <div class="date-holder">' + getArticleHtml(article, 'pubDate') + '</div>' + 66 ' <p>' + getArticleHtml(article, 'description') + '</p>' + 67 ' <p><a href="' + getArticleText(article, 'link') + '" class="btn btn-clean dark-gray">' + window.t('Lees dit bericht') + ' <i class="fas fa-chevron-right"></i></a></p>' + 68 ' </div>' + 69 ' </div>' + 70 '</article>'; 71 } 72 73 }); 74 75 $('#blog-feed').append(html); 76 77 } else { 78 $('#blog-feed').empty(); 79 $('#blog-feed').append( 80 '<div class="w-100 pt-3 pb-4 d-flex justify-content-center">' + window.t('Geen blogs gevonden.') + '</div>' 81 ); 82 } 83 84 }) 85 .catch(function () { 86 $('#blog-feed').empty(); 87 $('#blog-feed').append( 88 '<div class="w-100 pt-3 pb-4 d-flex justify-content-center">' + window.t('Geen blogs gevonden.') + '</div>' 89 ); 90 }); 91 92 } 93 94 /** 95 * Remove HTML style attributes so CSP style-src (nonce, no 'unsafe-inline') is not violated. 96 * 97 * @param {string} strHtml 98 * @return {string} 99 */ 100 function stripInlineStyles(strHtml) { 101 102 if (typeof strHtml !== 'string') { 103 return strHtml; 104 } 105 106 return strHtml 107 .replace(/\sstyle\s*=\s*"[^"]*"/gi, '') 108 .replace(/\sstyle\s*=\s*'[^']*'/gi, '') 109 .replace(/\sstyle\s*=\s*[^\s>]+/gi, ''); 110 111 } 112 113 function getArticleHtml(article, selector) { 114 var element = article.querySelector(selector); 115 116 return element ? element.innerHTML : ''; 117 } 118 119 function getArticleText(article, selector) { 120 var element = article.querySelector(selector); 121 122 return element ? element.textContent : ''; 123 } 124 125 function getThumbnailHtml(article) { 126 var thumbnail = article.querySelector('thumbnail'); 127 128 if (!thumbnail) { 129 return ''; 130 } 131 132 var strHtml = stripInlineStyles(thumbnail.innerHTML); 133 var strTitle = getArticleText(article, 'title'); 134 135 // Decorative empty alt plus a title attribute is announced twice by some screen readers. 136 // Use the post title as alt instead. 137 return strHtml.replace(/<img\b([^>]*)>/i, function (strMatch, strAttrs) { 138 var strCleanAttrs = strAttrs 139 .replace(/\s+alt\s*=\s*("[^"]*"|'[^']*')/i, '') 140 .replace(/\s+title\s*=\s*("[^"]*"|'[^']*')/i, ''); 141 142 return '<img alt="' + escapeHtmlAttr(strTitle) + '"' + strCleanAttrs + '>'; 143 }); 144 } 145 146 /** 147 * Escape a string for use inside a double-quoted HTML attribute. 148 * 149 * @param {string} strValue 150 * @return {string} 151 */ 152 function escapeHtmlAttr(strValue) { 153 return String(strValue) 154 .replace(/&/g, '&') 155 .replace(/"/g, '"') 156 .replace(/</g, '<') 157 .replace(/>/g, '>'); 158 } 159 160 function downloadJSAtOnload2() { 161 var src = document.body.getAttribute('data-domain-checker-src'); 162 if (!src) { 163 return; 164 } 165 var element = document.createElement('script'); 166 element.src = src; 167 document.body.appendChild(element); 168 } 169 170 if (window.addEventListener) { 171 window.addEventListener('load', downloadJSAtOnload2, false); 172 } else if (window.attachEvent) { 173 window.attachEvent('onload', downloadJSAtOnload2); 174 } else { 175 window.onload = downloadJSAtOnload2; 176 } 177 178})(window.jQuery);
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.