1document.addEventListener("DOMContentLoaded", () => { 2 if (typeof FinanceHelperData === "undefined" || !FinanceHelperData.apiUrl) { 3 console.warn("FinanceHelperData.apiUrl not defined"); 4 return; 5 } 6 7 let apiData = null; // cache the API response 8 9 const fetchData = async () => { 10 if (apiData) return apiData; // already fetched once 11 try { 12 const res = await fetch(FinanceHelperData.apiUrl); 13 const data = await res.json(); 14 apiData = data?.["inbroker-transactions"]?.row || []; 15 return apiData; 16 } catch (err) { 17 console.error("Finance Helper API fetch error:", err); 18 return []; 19 } 20 }; 21 22 const observer = new IntersectionObserver(async (entries, obs) => { 23 const allData = await fetchData(); // fetch once 24 25 entries.forEach(entry => { 26 if (entry.isIntersecting) { 27 const el = entry.target; 28 const stockSlug = el.getAttribute("data-stock"); 29 const span = el.querySelector(".card__finance-stocks-item--up, .card__finance-stocks-item--down") || 30 el.querySelector("span"); 31 32 if (!stockSlug || !span) return; 33 34 const stock = allData.find(item => item.instrSysName === stockSlug); 35 if (stock && typeof stock.closePrevClosePDelta !== "undefined") { 36 const stockPrice = (stock.closePrice > 0 ? 37 stock.closePrevClosePDelta : 38 (stock.price > 0 ? stock.pricePrevClosePricePDelta : 0)); 39 40 const value = parseFloat(stockPrice); 41 span.textContent = `${value > 0 ? '+' : ''}${value.toFixed(2)}%`; 42 span.classList.remove("card__finance-stocks-item--up", "card__finance-stocks-item--down"); 43 span.classList.add(value > 0 ? "card__finance-stocks-item--up" : "card__finance-stocks-item--down"); 44 } else { 45 span.textContent = "N/A"; 46 } 47 48 obs.unobserve(el); 49 } 50 }); 51 }, {threshold: 0.2}); 52 53 document.querySelectorAll(".card__finance-stocks").forEach(el => observer.observe(el)); 54 55 56 const quoteBlocks = document.querySelectorAll('.fh-quotes'); 57 if (!quoteBlocks.length) return; 58 59 // Fetch data once per page 60 fetch(FinanceHelperData.apiUrl) 61 .then(res => res.json()) 62 .then(data => { 63 const rows = data?.['inbroker-transactions']?.row || []; 64 65 quoteBlocks.forEach(block => { 66 const order = block.dataset.order || 'asc'; 67 let sorted; 68 69 // Sort based on order type 70 if (order === 'turnover') { 71 // Sort by max turnover descending 72 sorted = [...rows].sort((a, b) => { 73 const turnoverA = parseFloat(a.totalTurnover) || 0; 74 const turnoverB = parseFloat(b.totalTurnover) || 0; 75 return turnoverB - turnoverA; 76 }); 77 } else { 78 // Sort by PDelta (asc or desc) 79 sorted = [...rows].sort((a, b) => { 80 const deltaA = parseFloat(a.pricePrevClosePricePDelta) || 0; 81 const deltaB = parseFloat(b.pricePrevClosePricePDelta) || 0; 82 return order === 'desc' ? deltaB - deltaA : deltaA - deltaB; 83 }); 84 } 85 86 // Limit to first 6 for performance 87 const topRows = sorted.slice(0, 6); 88 89 let html, headerHtml; 90 91 if (order === 'turnover') { 92 // Turnover mode: show symbol, turnover, delta 93 headerHtml = ` 94 <div class="fh-quote"> 95 <div class="fh-quote__symbol"><strong>ÎεÏοÏή</strong></div> 96 <div class="fh-quote__turnover"><strong>ΤζίÏοÏ</strong></div> 97 <div class="fh-quote__delta"><strong>ÎεÏαβολή</strong></div> 98 </div>`; 99 100 html = topRows.map(row => { 101 const deltaValue = parseFloat(row.pricePrevClosePricePDelta).toFixed(3); 102 const delta = Number(deltaValue).toLocaleStr
102ing('el-GR', {maximumFractionDigits: 2}); 103 const isUp = deltaValue >= 0; 104 const deltaClass = isUp ? 'fh-quote__delta--up' : 'fh-quote__delta--down'; 105 const sign = isUp ? '+' : ''; 106 const turnover = Number(row.totalTurnover).toLocaleString('el-GR', {maximumFractionDigits: 2}); 107 108 return ` 109 <div class="fh-quote"> 110 <a href="/quote/${row.instrSysName}" class="fh-quote__symbol">${row.instrCode}</a> 111 <div class="fh-quote__turnover">${turnover} â¬</div> 112 <div class="fh-quote__delta ${deltaClass}">${sign}${delta}%</div> 113 </div>`; 114 }).join(''); 115 } else { 116 // Asc/Desc mode: show symbol, price, delta (no turnover) 117 headerHtml = ` 118 <div class="fh-quote"> 119 <div class="fh-quote__symbol"><strong>ÎεÏοÏή</strong></div> 120 <div class="fh-quote__price"><strong>Τιμή</strong></div> 121 <div class="fh-quote__delta"><strong>ÎεÏ</strong></div> 122 <div class="fh-quote__delta"><strong>ÎεÏ%</strong></div> 123 </div>`; 124 125 html = topRows.map(row => { 126 const price = Number(row.price || row.closePrice || 0).toLocaleString('el-GR', {maximumFractionDigits: 2}); 127 const deltaPValue = parseFloat(row.pricePrevClosePricePDelta || 0); 128 const deltaValue = parseFloat(row.pricePrevClosePriceDelta || 0); 129 const delta = Number(deltaValue).toLocaleString('el-GR', {maximumFractionDigits: 2}); 130 const deltaP = Number(deltaPValue).toLocaleString('el-GR', {maximumFractionDigits: 2}); 131 const isUp = deltaPValue >= 0; 132 const deltaClass = isUp ? 'fh-quote__delta--up' : 'fh-quote__delta--down'; 133 const sign = isUp ? '+' : ''; 134 135 return ` 136 <div class="fh-quote"> 137 <a href="/quote/${row.instrSysName}" class="fh-quote__symbol">${row.instrCode}</a> 138 <div class="fh-quote__price">${price}</div> 139 <div class="fh-quote__delta ${deltaClass}">${sign}${delta}</div> 140 <div class="fh-quote__delta ${deltaClass}">${sign}${deltaP}%</div> 141 </div>`; 142 }).join(''); 143 } 144 145 block.innerHTML = `<div class="fh-quotes__grid">${headerHtml}${html}</div>`; 146 }); 147 }) 148 .catch(() => { 149 quoteBlocks.forEach(block => { 150 block.innerHTML = `<div class="fh-quotes__error">ΣÏάλμα ÏÏÏÏÏÏÎ·Ï Î´ÎµÎ´Î¿Î¼ÎνÏν.</div>`; 151 }); 152 }); 153}); 154 155var tickerContent = []; 156tickerContent['ticker-metoxes'] = ''; 157 158HTMLElement.prototype.marquee = function (args = {count: -1, speed: 17, leftToRight: false}) { 159 const element = this; 160 element.classList.add('marquee-started'); 161 162 const textWidth = element.scrollWidth; 163 let offset = 0; 164 let width = offset; 165 const css = { 166 transform: element.style.transform, 167 overflow: element.style.overflow, 168 whiteSpace: element.style.whiteSpace, 169 }; 170 const marqueeCss = { 171 transform: `translate(${width}px)`, 172 overflow: 'hidden', 173 whiteSpace: 'nowrap', 174 }; 175 let i = 0; 176 let stop = textWidth * -1; 177 let isRunning = true; 178 179 // Apply marquee styling to start 180 Object.assign(element.style, marqueeCss); 181 182 // The main animation function 183 function go() { 184 // Stop condition based on max-height 185 if (getComputedStyle(element).maxHeight === '999998px') { 186 console.log('STOPPED DUE TO SEARCH'); 187 Object.assign(element.style, css); // Restore original styles 188 isRunning = false;
189 return; 190 } 191 192 // Check if element exists; stop if it doesnât 193 if (!document.body.contains(element)) { 194 isRunning = false; 195 return; 196 } 197 198 // Reset width for continuous scrolling 199 if (width === stop) { 200 i++; 201 if (i === args.count) { 202 Object.assign(element.style, css); // Restore original styles 203 isRunning = false; 204 return; 205 } 206 width = args.leftToRight ? -textWidth : offset; 207 } 208 209 // Update position 210 element.style.transform = `translate(${width}px)`; 211 212 // Increment or decrement width based on scroll direction 213 if (getComputedStyle(element).maxHeight !== '999999px') { 214 width += args.leftToRight ? 1 : -1; 215 } 216 217 if (isRunning) { 218 setTimeout(go, args.speed); 219 } 220 } 221 222 // Initialize width and stop positions based on direction 223 if (args.leftToRight) { 224 width = -textWidth + 1; 225 stop = offset; 226 } else { 227 width -= 1; 228 } 229 230 // Start animation 231 go(); 232}; 233 234function nxFormatPrice(x) { 235 return x.toString().replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.'); 236} 237 238function nxRoundTo(n, digits) { 239 if (digits === undefined) { 240 digits = 0; 241 } 242 243 var multiplicator = Math.pow(10, digits); 244 n = parseFloat((n * multiplicator).toFixed(11)); 245 return Math.round(n) / multiplicator; 246} 247 248function nxJumpToTickerResult(el) { 249 let actTicker = document.querySelector('.output-wrapper.active'); 250 actTicker.style.maxHeight = '999998px'; 251 if (actTicker.classList.contains('marquee-started')) { 252 actTicker.classList.remove('marquee-started'); 253 actTicker.classList.add('search-performed'); 254 } 255 setTimeout(function () { 256 actTicker.style.transform = 'translate(' + (parseFloat(el.offsetLeft - 10) * -1) + 'px)'; 257 }, 100); 258 259 if (!el.classList.contains('nx-symbol-found')) { 260 let cel = el.querySelector('span'); 261 cel.classList.add('nx-symbol-found'); 262 setTimeout(function () { 263 cel.classList.remove('nx-symbol-found'); 264 }, 2500); 265 } 266} 267 268function nxNotFoundTickerResult() { 269 let el = document.querySelector('#search-ticker'); 270 if (!el.classList.contains('nx-symbol-not-found')) { 271 let cel = el.querySelector('span'); 272 cel.classList.add('nx-symbol-not-found'); 273 cel.textContent = 'ÎÎÎ ÎΡÎÎÎÎÎ'; 274 document.querySelector('#search-ticker-input').disabled = true; 275 cel.disabled = true; 276 setTimeout(function () { 277 cel.classList.remove('nx-symbol-not-found'); 278 cel.textContent = 'ÎÎÎÎÎΤÎΣÎ'; 279 cel.disabled = false; 280 document.querySelector('#search-ticker-input').disabled = false; 281 document.querySelector('#search-ticker-input').value = ''; 282 document.querySelector('#search-ticker-input').focus(); 283 }, 1500); 284 } 285} 286 287function nxTickerSearch(searchStr) { 288 var BreakException = {}; 289 var nxTickerSearchFound = false; 290 if (searchStr) { 291 let upperSearchStr = searchStr.toUpperCase(); 292 try { 293 [].forEach.call( 294 document.querySelectorAll('.output-wrapper.active span'), 295 function (el) { 296 let tC = el.textContent.trim().toUpperCase(); 297 console.log('SEARCHING FOR ' + upperSearchStr); 298 let firstResult = tC.lastIndexOf(upperSearchStr, 0) === 0; 299 if (firstResult) { 300 // TRY BEGINS WITH 301 nxJumpToTickerResult(el); 302 nxTickerSearchFound = true; 303 throw BreakException; 304 } else { 305 // TRY CONTAINED 306 if (tC.indexOf(upperSearchStr) !== -1) { 307 nxJumpToTickerResult(el); 308 nxTickerSearchFound = true; 309 throw BreakException; 310 } 311 } 312 }, 313 ); 314 } catch (e) { 315 if (e !== BreakException) throw e; 316 } 317 if (!nxTickerSearchFound) { 318 nxNotFoundTickerResult(); 319 } 320 } 321 322} 323
324//////////////// 325// TICKERS START 326if (typeof nx_handleTickerData !== 'function') { 327 function nx_handleTickerData(IBData, tickerName, elementIDs) { 328 329 let itemName, itemCurrPrice, itemCurrChange, itemCurrChangeP, itemSysName, itemCatSysName; 330 let nxObj = IBData; 331 332 let addPlus = false; 333 let currPrice, currChange, txt; 334 tickerContent[tickerName] = ''; 335 336 if (typeof nxObj !== 'undefined' && nxObj.hasOwnProperty('inbroker-transactions') && 337 nxObj['inbroker-transactions'].hasOwnProperty('row')) { // FAIL-SAFE CHECK 338 339 let IBObj = nxObj['inbroker-transactions']['row']; 340 341 if (IBObj.id) { 342 console.log('mpika: ', IBObj); 343 itemName = IBObj.instrCode; 344 itemSysName = IBObj.instrSysName; 345 itemCatSysName = IBObj.categorySysName; 346 itemCurrPrice = (IBObj.closePrice > 0 ? 347 IBObj.closePrice : 348 (IBObj.price > 0 ? IBObj.price : IBObj.prevClosePrice)); 349 itemCurrChange = (IBObj.closePrice > 0 ? 350 IBObj.closePrevCloseDelta : 351 (IBObj.price > 0 ? IBObj.pricePrevClosePriceDelta : 0)); 352 itemCurrChangeP = (IBObj.closePrice > 0 ? 353 IBObj.closePrevClosePDelta : 354 (IBObj.price > 0 ? IBObj.pricePrevClosePricePDelta : 0)); 355 356 // CHANGE FOR LIVE: 357 txt = (parseFloat(itemCurrChange) > 0 ? 358 'arrow-up' : 359 (parseFloat(itemCurrChange) < 0 ? 'arrow-down' : 'no-arrow')); 360 if (tickerName !== 'ticker-omologa' || 361 (tickerName === 'ticker-omologa' && itemCatSysName.substring(0, 10) === 'CORPORATE.')) { 362 tickerContent[tickerName] += ` 363 <span class="ticker-item"> 364 <a href="/quote/${itemSysName}/"> 365 <span><strong>${itemName}</strong></span> 366 <span class="${txt} ${txt}bg">${itemCurrPrice}</span> 367 <span class="${txt}bg">${nxRoundTo(itemCurrChangeP, 2)}%</span> 368 </a> 369 </span> 370 `; 371 } 372 } else { 373 let rowObj = {}; 374 for (let i = 0; i < IBObj.length; i++) { 375 rowObj = IBObj[i]; 376 377 itemName = rowObj.instrCode; 378 itemSysName = rowObj.instrSysName; 379 itemCatSysName = rowObj.categorySysName; 380 381 itemCurrPrice = (rowObj.closePrice > 0 ? 382 rowObj.closePrice : 383 (rowObj.price > 0 ? rowObj.price : rowObj.prevClosePrice)); 384 385 itemCurrChange = (rowObj.closePrice > 0 ? 386 rowObj.closePrevCloseDelta : 387 (rowObj.price > 0 ? rowObj.pricePrevClosePriceDelta : 0)); 388 389 itemCurrChangeP = (rowObj.closePrice > 0 ? 390 rowObj.closePrevClosePDelta : 391 (rowObj.price > 0 ? rowObj.pricePrevClosePricePDelta : 0)); 392 393 // CHANGE FOR LIVE: 394 txt = (parseFloat(itemCurrChange) > 0 ? 395 'arrow-up' : 396 (parseFloat(itemCurrChange) < 0 ? 'arrow-down' : 'no-arrow')); 397 398 tickerContent[tickerName] += ` 399 <span class="ticker-item"> 400 <a href="/quote/${itemSysName}/"> 401 <span><strong>${itemName}</strong></span> 402 <span class="${txt} ${txt}bg">${itemCurrPrice}</span> 403 <span class="${txt}bg">${nxRoundTo(itemCurrChangeP, 2)}%</span> 404 </a> 405 </span> 406 `; 407 } 408 } 409 nxConstuctTicker(tickerName); 410 } 411 } 412} 413 414function nxConstuctTicker(tickerName) { 415 let currTickerClass = tickerName + '-output', 416 currTicker = document.querySelector('.' + currTickerClass),
417 tmpTickerClass, tmpTicker, nxSkip, loopTicker, tmpVar, 418 nxTickers = ['ticker-metoxes']; 419 420 for (let i = 0; i < nxTickers.length; i++) { 421 loopTicker = nxTickers[i] + '-output'; 422 423 if (tickerName === nxTickers[i]) { 424 if (!currTicker.classList.contains('active')) { 425 currTicker.classList.add('active'); 426 } 427 if (!currTicker.classList.contains('is-init')) { 428 currTicker.setAttribute('data-speed', '0.45'); 429 currTicker.setAttribute('data-pausable', 'true'); 430 currTicker.innerHTML = `<div>${tickerContent[tickerName]}</div>`; 431 } 432 let tmpVar = document.querySelector('.' + currTickerClass); 433 if (!tmpVar.classList.contains('marquee-started') && !tmpVar.classList.contains('search-performed')) { 434 tmpVar.marquee(); 435 } else { 436 if (tmpVar.classList.contains('marquee-started')) { 437 console.log('TICKER ' + tickerName + ' NOT STARTED ALREADY RUNNING'); 438 } 439 if (tmpVar.classList.contains('search-performed')) { 440 console.log('TICKER ' + tickerName + ' NOT STARTED BLOCKED BY SEARCH'); 441 } 442 } 443 } else { 444 tmpTickerClass = nxTickers[i] + '-output'; 445 console.log(tmpTickerClass); 446 tmpTicker = document.querySelector('.' + tmpTickerClass); 447 if (tmpTicker.classList.contains('active')) { 448 tmpTicker.classList.remove('active'); 449 } 450 } 451 } 452} 453 454let nxTickerMap = []; 455nxTickerMap['ticker-metoxes'] = 'EQ'; 456 457fetch(FinanceHelperData.apiUrl) 458 .then(res => res.json()) 459 .then(data => { 460 console.log(data); 461 nx_handleTickerData(data, 'ticker-metoxes') 462 }) 463 .catch((err) => { 464 console.error("Finance Helper API fetch error:", err); 465 }); 466 467// Load World Markets Stocks Ticker 468function loadWorldMarketsTicker() { 469 fetch('/feeds/indices.html') 470 .then(response => { 471 if (!response.ok) { 472 throw new Error('Network response was not ok'); 473 } 474 return response.text(); 475 }) 476 .then(html => { 477 const stocksContainer = document.querySelector('.nav__stocks.container'); 478 if (stocksContainer) { 479 stocksContainer.innerHTML = html; 480 } 481 }) 482 .catch(error => { 483 console.error('Error loading world markets ticker:', error); 484 }); 485} 486 487// Load on page load 488if (document.readyState === 'loading') { 489 document.addEventListener('DOMContentLoaded', loadWorldMarketsTicker); 490} else { 491 loadWorldMarketsTicker(); 492} 493 494// Optionally reload every 5 minutes (300000 ms) 495setInterval(loadWorldMarketsTicker, 300000);
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.