1// displays a pop-up when you hover over a link 2 3// at page load, add new mouse events to all internal page links 4document.addEventListener("DOMContentLoaded", function() { 5 Add_Preview_To_Links(); 6}); 7 8// a separate function so it can be called when marker links are added 9// to pages 10function Add_Preview_To_Links(srcel=document) { 11 let lnks = srcel.getElementsByTagName('a'); 12 let base = window.location.origin+'/'; // eg https://canalplan.uk/ 13 Object.values(lnks).forEach(function(lnk) { 14 if(lnk.href.indexOf(base) == 0) { // it's an internal link 15 if(!lnk.dataset.previewenabled) { 16 let lk = lnk.href.substr(base.length).split('/'); 17 if(['area','feature','place','waterway','boat','location'].includes(lk[0])) { 18 if(lk[0] == 'location') 19 lk = Preview_Resolve_Location(lk[1]); 20 if(lk[1].indexOf('$')==-1) { // don't do magic ones 21 // it's a link to a page 22 lnk.addEventListener("mouseover", e=> { 23 Show_Preview(lnk, lk[0], lk[1], e); 24 }); 25 lnk.addEventListener("mouseout", e=> { 26 Hide_Preview(); 27 }); 28 lnk.dataset.previewenabled = true; 29 } 30 } 31 } 32 } 33 }); 34 // now add preview to navigational notes 35 document.querySelectorAll('.navnotelink').forEach(function(lnk) { 36 if(!lnk.dataset.previewenabled) { 37 lnk.addEventListener("mouseover", e=> { 38 Show_Preview(lnk, 'navnote', lnk.dataset.nnnum, e); 39 }); 40 lnk.addEventListener("mouseout", e=> { 41 Hide_Preview(); 42 }); 43 lnk.dataset.previewenabled = true; 44 } 45 }); 46 // element to pu 47 let preview = document.createElement('div'); 48 preview.id = 'preview_pane'; 49 preview.classList.add('link_preview_pane'); 50 document.body.appendChild(preview); 51} 52 53function Preview_Resolve_Location(loc) { 54 if(loc.indexOf(':') == -1) // plain place 55 return ['place', loc]; 56 let wk = loc.split(':'); 57 switch(wk[0]) { 58 case 'a': 59 return ['area', wk[1]]; 60 case 'f': 61 case 's': 62 return ['feature', wk[1]]; 63 case 'w': 64 case 'ww': 65 return ['waterway', wk[1]]; 66 case 'p': 67 return ['place', wk[1]]; 68 default: 69 throw 'Unknown location code:'+loc 70 } 71} 72 73// we want to be sure you are actually hovering, not just passing through 74// avoids problem when you move the mouse over a list of links and queue 75// dozens of previews! 76var preview_timeout = null; 77 78function Show_Preview(anchor, type, id, mouse) { 79 let preview = $$('preview_pane'); 80 clearTimeout(preview_timeout); // kill any previous timeouts 81 preview_timeout = setTimeout(function() { 82 Fetch_Preview(type, id).then(txt=>{ 83 preview.style.visibility = 'visible'; 84 preview.innerHTML = txt; 85 // this means that we position it correctly after all in internals 86 // have been rendered 87 const resizeObserver = new ResizeObserver(entries => { 88 let previewbox = preview.getBoundingClientRect(); 89 let anchorbox = anchor.getBoundingClientRect(); 90 if(mouse.clientX < window.innerWidth/2) { // left half of screen 91 x = mouse.clientX; 92 } else { // right half of screen 93 x = mouse.clientX - previewbox.width; 94 } 95 // we use the two mouse Y coordinates to work out what offset 96 // the screen has! 97 let yoff = mouse.layerY - mouse.clientY; 98 if(mouse.clientY < window.innerHeight/2) { // top half of screen 99 // vertical has a small offset to move off the text 100 y = anchorbox.bottom + yoff + 10; 101 } else { // bottom half of screen 102 y = anchorbox.bottom + yoff - previewbox.height - 10; 103 } 104 preview.style.left = x+"px"; 105 preview.style.top = y+"px"; 106 }); 107 resizeObserver.observe(preview); 108 }); 109 }, 200); // seems to be reasonable short delay 110} 111 112function Hide_Preview() { 113 clearTimeout(preview_timeout); 114 let preview = $$('preview_pane'); 115 preview.innerHTML = ''; 116 preview.style.visibility = 'hidden'; 117} 118 119var link_preview_cache = {}; 120 121async function Fetch_Preview(type, id) { 122 if(link_preview_cache[type+':'+id]) { 123 return link_preview_cache[type+':'+id]; 124 } 125 var mtab = { 126 area: {frompage: true}, 127 boat: {frompage: false}, 128 feature: {frompage: true}, 129 place: {frompage: true}, 130 waterway: {frompage: true}, 131 navnote: {frompage: false} 132 }; 133 if(mtab[type].frompage) { 134 await fetch("/"+type+"/"+id) 135 .then(data => { 136 if(data.ok) 137 return data.text(); 138 throw new Error('Fetch failed for page "'+type+'/'+id+'"'); 139 }).then(res => { 140 var parser = new DOMParser(); 141 var newdoc = parser.parseFromString(res,"text/html"); 142 let parts = Useful_Elements(newdoc); 143 link_preview_cache[type+':'+id] = Format_Preview(parts); 144 }); 145 return link_preview_cache[type+':'+id]; 146 } else { 147 if(type == 'boat') { 148 await fetch("/render/boat_info?nobutton=yes&id="+id) 149 .then(data => { 150 if(data.ok) 151 return data.text(); 152 throw new Error('Fetch failed for remote_render'); 153 }).then(res => { 154 link_preview_cache["boat:"+id] = res; 155 }); 156 return link_preview_cache["boat:"+id]; 157 } else { // navnote 158 // we have it in the page, so no need to fetch 159 let nhtml1 = document.getElementById('navnote-header-'+id); 160 let nhtml2 = document.getElementById('navnote-body-'+id); 161 let parts = Useful_Elements_Navnote(nhtml1, nhtml2); 162 link_preview_cache[type+':'+id] = Format_Preview(parts); 163 return link_preview_cache[type+':'+id]; 164 } 165 } 166} 167 168function Preview_Remove_Links(el) { 169 let lnks = el.getElementsByTagName('a');
170 Object.values(lnks).forEach(function(lnk) { 171 lnk.classList.remove('quiet'); 172 lnk.classList.add('preview_hide_link'); 173 }); 174 return el; 175} 176 177// pulls elements we are likely to need from the page HTML 178function Useful_Elements(dom) { 179 var toret = {}; 180 [ 181 {el:'place_name_show',op:'innerHTML',as:'name'}, 182 {el:'place_detail_show',op:'innerHTML',as:'detail'}, 183 {el:'best_photo',op:'src',as:'thumbnail'}, 184 {el:'pagecloseupmapimg',op:'src',as:'circlemap'}, 185 {el:'summary_description',op:'innerHTML',as:'description',call:Preview_Remove_Links} 186 ].forEach(i=> { 187 let item = dom.getElementById(i.el); 188 if(item) { 189 if(i.call) 190 item = i.call(item); 191 toret[i.as] = item[i.op]; 192 } 193 }); 194 return toret; 195} 196 197function Useful_Elements_Navnote(header, body) { 198 let toret = {}; 199 200 let nname = header.getElementsByClassName('navnote-name'); 201 toret.name = nname[0].childNodes[0].innerHTML; 202 203 let nmap = body.getElementsByClassName('cc_map'); 204 toret.circlemap = nmap[0].src; 205 206 let nphoto = body.getElementsByClassName('navigational_photo'); 207 if(nphoto.length) { 208 toret.thumbnail = nphoto[0].childNodes[0].src; 209 toret.thumbnail = toret.thumbnail.replace('200x200', 'thumbnails'); 210 } else 211 toret.thumbnail = null; 212 213 let ntext = body.getElementsByClassName('vertbox-inner'); 214 toret.description = ntext[0].childNodes[0].wholeText; 215 return toret; 216} 217 218function Format_Preview(items) { 219 let txt = ` 220<div class="link_preview_flex_row"> 221<div class="link_preview_flex_item">${items.thumbnail ? `<img src="${items.thumbnail}">` : ""}</div> 222<div class="link_preview_flex_item"><h1>${items.name}</h1>${items.detail || ''}</div> 223<div class="link_preview_flex_item">${items.circlemap ? `<img class="cc_map link_preview_map" src="${items.circlemap}">` : ""}</div> 224</div> 225<div class="link_preview_text">${items.description}</div>`; 226 return txt 227} 228
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.