1var mapLatlng = { lat: 54.59, lng: - 5.93 }; 2var mapOptions = { zoom: 2, center: mapLatlng, mapTypeId: 'roadmap' }; 3var BelfastLat = 54.5948; 4var BelfastLng = -5.926; 5var VBMTHex = "#6af400"; 6var VBMTHexAlt = "#F40000"; 7var EntityName = "Mural"; 8function placeMarker(location, text, url) { 9 var marker = new google.maps.Marker({ position: location, map: map, title: text }); 10 google.maps.event.addListener(marker, 'click', function () { 11 window.location = url; 12 }); 13} 14function print_mail_to_link() { 15 document.write("<a href=\"mailto"); 16 document.write(":" + lhs + "@"); 17 document.write(rhs + "\">" + lhs + "@" + rhs + "<\/a>"); 18} 19 20function setSelectedIndex(s, v) { 21 for (var i = 0; i < s.options.length; i++) { 22 if (s.options[i].value == v) { 23 s.options[i].selected = true; 24 return; 25 } 26 } 27} 28 29function sendcr(table, tableid, field, newvalue, reason, isencoded) { 30 var rn = Math.floor(Math.random() * 9999); 31 32 $.ajax({ 33 url: 'request/addcr.php', 34 type: "POST", 35 data: { 36 rn: rn, 37 cr_table: table, 38 cr_tableid: tableid, 39 cr_field: field, 40 cr_new: newvalue, 41 cr_reason: reason, 42 isencoded: isencoded 43 }, 44 success: function (result) { 45 //console.log(result); 46 }, 47 error: function (x, y, z) { 48 console.log(y); 49 } 50 }); 51} 52 53const monthNames = ["January", "February", "March", "April", "May", "June",
54 "July", "August", "September", "October", "November", "December" 55]; 56 57const nth = (d) => { 58 if (d > 3 && d < 21) return 'th'; 59 switch (d % 10) { 60 case 1: return "st"; 61 case 2: return "nd"; 62 case 3: return "rd"; 63 default: return "th"; 64 } 65}; 66 67 68function urldecode(str) { 69 let newStr = ''; 70 const len = str.length; 71 72 for (let i = 0; i < len; i++) { 73 let c = str.charAt(i); 74 75 if (c === '+') { 76 newStr += ' '; 77 } 78 else if (c === '%') { 79 const hex = str.substr(i + 1, 2); 80 const code = parseInt(hex, 16); 81 newStr += String.fromCharCode(code); 82 i += 2; 83 } 84 else { 85 newStr += c; 86 } 87 } 88 89 return newStr; 90} 91 92 93 94 95// ------------------------------ 96// Photo attribution / citation 97// ------------------------------ 98document.addEventListener('DOMContentLoaded', function () { 99 function getCanonicalUrl() { 100 const link = document.querySelector('link[rel=canonical]'); 101 return link && link.href ? link.href : window.location.href.split('#')[0]; 102 } 103 104 const rawPageTitle = (document.title || '').trim(); 105 // Strip a trailing "in Belfast" or "Mural in Belfast" (case-insensitive) 106 const cleanedTitle = rawPageTitle.replace(/\s+(?:Mural\s+)?in Belfast\s*$/i, ''); 107 const pageTitle = cleanedTitle; 108 const pageUrl = getCanonicalUrl(); 109 110 const caps = document.querySelectorAll('.photo-credit'); 111 // console.log('[photo-credit] found:', caps.length); 112 113 caps.forEach(function (cap, idx) { 114 const author = (cap.dataset.author || 'Virtual Belfast Mural Tour').trim(); 115 const license = (cap.dataset.license || 'Free to use with attribution').trim(); 116 const licenseUrl = (cap.dataset.licenseUrl || '').trim(); 117 const workTitle = (cap.dataset.workTitle || '').trim(); 118 119 // Use workTitle if present, otherwise pageTitle (with suffix stripped) 120 const citeTitle = (workTitle || pageTitle) + ' - Virtual Belfast Mural Tour'; 121 const licenseHtml = licenseUrl 122 ? '<a href="' + licenseUrl + '" rel="license noopener noreferrer">' + license + '</a>' 123 : license; 124 125 // Unique id for accessibility 126 const bodyId = 'photo-attr-body-' + idx; 127 128 cap.innerHTML = '' 129 + '<details class="photo-attr">' 130 + ' <summary class="photo-attr__summary" role="button" aria-controls="' + bodyId + '">' 131 + ' Photo Attribution/Citation Details' 132 + ' </summary>' 133 + ' <div class="photo-attr__body" id="' + bodyId + '">' 134 + ' <span>Photo by <span class="photo-author">' + author + '</span>.</span> ' 135 + ' <span> Please credit: "<span class="photo-cite-title">' + citeTitle + '</span>"</span> ' 136 + ' <span> (<a href="' + pageUrl + '" rel="noopener noreferrer">' + pageUrl + '</a>).</span> ' 137 + ' <span> License: ' + licenseHtml + '.</span> ' 138 + ' <button type="button" class="copy-citation" aria-label="Copy citation">Copy</button>' 139 + ' </div>' 140 + '</details>'; 141 142 const btn = cap.querySelector('.copy-citation'); 143 btn.addEventListener('click', function (e) { 144 e.preventDefault(); 145 const text = 'Photo by ' + author + '. Please credit: â' + citeTitle + 'â (' + pageUrl + '). ' 146 + 'License: ' + license + (licenseUrl ? ' â ' + licenseUrl : '') + '.'; 147 copyText(text, btn).finally(function () { 148 setTimeout(function () { btn.textContent = 'Copy'; }, 1500); 149 }); 150 }); 151 }); 152}); 153 154function copyText(text, btn) { 155 // Prefer the async Clipboard API when available + secure 156 if (navigator.clipboard && window.isSecureContext) { 157 return navigator.clipboard.writeText(text) 158 .then(() => { btn.textContent = 'Copied!'; }) 159 .catch((err) => { 160 console.warn('[copy] clipboard.writeText failed:', err); 161 return legacyCopy(text, btn); 162 }); 163 } else { 164 return legacyCopy(text, btn); 165 } 166 167 function legacyCopy(txt, buttonEl) { 168 try { 169 const ta = document.createElement('textarea'); 170 ta.value = txt; 171 // Keep off-screen but selectable 172 ta.style.position = 'fixed'; 173 ta.style.top = '-1000px'; 174 ta.style.opacity = '0'; 175 document.body.appendChild(ta); 176 177 // iOS needs a special selection flow 178 if (/ipad|iphone|ipod/i.test(navigator.userAgent)) { 179 const range = document.createRange(); 180 range.selectNodeContents(ta); 181 const sel = window.getSelection(); 182 sel.removeAllRanges(); 183 sel.addRange(range); 184 ta.setSelectionRange(0, ta.value.length); 185 } else { 186 ta.select(); 187 } 188 189 const ok = document.execCommand('copy'); 190 document.body.removeChild(ta); 191 192 if (ok) { 193 buttonEl.textContent = 'Copied!'; 194 return Promise.resolve(); 195 } else { 196 throw new Error('execCommand returned false'); 197 } 198 } catch (e) { 199 console.warn('[copy] execCommand fallback failed:', e); 200 alert('Sorry, your browser blocked copying. Please select and copy manually.'); 201 return Promise.reject(e); 202 } 203 } 204} 205 206 207
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.