1const searchModule = () => { 2 const data = { 3 suggestData: null, 4 suggestResult: { 5 stocks: [], 6 extend: [], 7 history: [], 8 }, 9 hotKeyword: '', 10 }; 11 const eventHandlers = {}; 12 const getEventHandler = (handleSelector) => { 13 if (!eventHandlers[handleSelector]) { 14 eventHandlers[handleSelector] = { 15 keypress(evt) { 16 let event = new CustomEvent('keypressInSearchInput', { 17 detail: { 18 target: evt.target, 19 key: evt.key, 20 }, 21 }); 22 if (evt.key == 'Enter') evt.preventDefault(); 23 document.querySelector(handleSelector).dispatchEvent(event); 24 }, 25 keydown(evt) { 26 if (evt.key == 'Enter') return; // Enter ä¸ç keydown 27 28 // ä¸ä¸éµåªèç鏿 29 if (evt.key == 'ArrowUp' || evt.key == 'ArrowDown') { 30 // é¿å 游æ¨è·æ 31 evt.preventDefault(); 32 let event = new CustomEvent('keydownInSearchInput', { 33 detail: { 34 target: evt.target, 35 key: evt.key, 36 }, 37 }); 38 document.querySelector(handleSelector).dispatchEvent(event); 39 return; 40 } 41 42 // å ¶å®çä¹ä¸å» keypress 43 this.keypress(evt); 44 }, 45 }; 46 } 47 return eventHandlers[handleSelector]; 48 }; 49 const setSuggestData = (obj) => { 50 data.suggestData = obj; 51 }; 52 const setSuggestResult = (obj) => { 53 data.suggestResult = obj; 54 }; 55 const setHotKeyword = (str) => {
56 data.hotKeyword = str; 57 }; 58 const getData = () => data; 59 return { 60 getData, 61 setSuggestData, 62 setSuggestResult, 63 setHotKeyword, 64 getEventHandler, 65 }; 66}; 67 68const searchApis = { 69 getSearchSuggestData: (apiurl, site_url) => { 70 return fetch(apiurl + '/api/getsuggest_cacheable.php') 71 .then((res) => res.json()) 72 .then((data) => { 73 let searchSuggest = JSON.parse(data['searchsuggest']) || []; 74 let autocompleteIndex = JSON.parse(data['autocompleteindex']) || []; 75 let searchStock = JSON.parse(data['searchstock']) || []; 76 return { 77 searchSuggest, 78 autocompleteIndex, 79 searchStock, 80 }; 81 }) 82 .catch((err) => { 83 console.log('é£ç·å¤±æ suggest'); 84 }); 85 }, 86 getAccountData: (action) => { 87 let formData = new FormData(); 88 formData.append('action', action); 89 90 return fetch(window.admin_url, { 91 method: 'POST', 92 body: formData, 93 headers: { 94 'User-Agent': 95 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36', 96 }, 97 }).then((res) => res.text()); 98 }, 99 getSearchRank: () => { 100 return fetch( 101 `${window.template_url}/api/getsearchsuggest_cacheable.php?v=` + Math.floor(new Date().getTime() / 1800000) 102 ) 103 .then((res) => res.json()) 104 .then((data) => { 105 sfUtils.shuffle(data); 106 return data; 107 }); 108 }, 109}; 110
111const searchControls = { 112 // _suggestData: null, 113 // setSuggestData: function (v) { 114 // this._suggestData = v; 115 // }, 116 // getStockIndexes: function () { 117 // if (this._suggestData && this._suggestData.searchstock) 118 // return this._suggestData.searchstock; 119 // return []; 120 // }, 121 // getCurrentStockIfAny: function () { 122 // let query = new URLSearchParams(window.location.search).get('query'); 123 // let stocksIndex = this.getStockIndexes(); 124 // if (!query || !stocksIndex[0]) return null; 125 // return stocksIndex.filter(item => { 126 // return item.stock_num.toLowerCase() === query 127 // || item.stock_name.toLowerCase() === query; 128 // })[0]; 129 // }, 130 // suggestDataReady: function () { 131 // this.isSuggestDataReady = true; 132 // }, 133 // waitForSuggestDataReady: async function () { 134 // return new Promise((resolve, reject)=>{ 135 // let cnt = 0; 136 // let tmer = setInterval(()=>{ 137 // cnt++; 138 // if (this.isSuggestDataReady) { 139 // resolve(); 140 // clearInterval(tmer); 141 // } 142 // if (cnt > 10) { 143 // reject(); 144 // clearInterval(tmer); 145 // } 146 // }, 200); 147 // }); 148 // }, 149 // callMenu å § function é寫 150 getSearchHistory: function () { 151 let row = getCookie('_sfSearchHistory'); 152 let history = JSON.parse(row) || []; 153 return history; 154 }, 155 setSearchHistory: function (query, history) { 156 let cookieArr = Array.from(new Set(history.unShift(query))).slice(0, 6); 157 let cookieQuery = JSON.stringify(cookieArr); 158 document.cookie = `_sfSearchHistory=${cookieQuery}; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/`; 159 }, 160 checkSearchType: function (str) { 161 if (str.match(/^[0-9]+$/g)) return 'NUM'; 162 else if (str.match(/^[A-Za-z]+$/g)) return 'EN'; 163 else return 'CH'; 164 }, 165 searchSuggestQuery: function (query, { autocompleteIndex }) { 166 return autocompleteIndex.filter((item) => item.indexOf(query) === 0).slice(0, 6); 167 }, 168 searchSuggestTerms: function (query, { searchSuggest }) { 169 const key = Object.keys(searchSuggest).find((item) => item.indexOf(query) === 0); 170 return searchSuggest[key] || []; 171 }, 172 searchSuggestStock: function (query, options, { searchStock }) { 173 const NUM_FLAG = 2, 174 NAME_FLAG = 1; 175 return searchStock 176 .filter((item) => { 177 let isNum, isName; 178 if (options & NUM_FLAG) { 179 isNum = item.stock_num.toLowerCase().indexOf(query.toLowerCase()) === 0; 180 } 181 if (options & NAME_FLAG) { 182 isName = item.stock_name.toLowerCase().indexOf(query.toLowerCase()) === 0; 183 } 184 return isNum || isName; 185 }) 186 .slice(0, 3); 187 }, 188 searchMatch: function (query, data) { 189 let stocks = [], 190 extend = []; 191 const { searchSuggestQuery, checkSearchType, searchSuggestTerms, searchSuggestStock } = this; 192 if (query.length === 1) { 193 extend = searchSuggestQuery(query, data); 194 } 195 if (query.length >= 2) { 196 const type = checkSearchType(query, data); 197 extend = searchSuggestTerms(query, data); 198 switch (type) { 199 case 'NUM': 200 stocks = searchSuggestStock(query, 2, data); 201 break; 202 case 'EN': 203 stocks = searchSuggestStock(query, 3, data); 204 break; 205 case 'CH': 206 default: 207 stocks = searchSuggestStock(query, 1, data); 208 break; 209 } 210 } 211 return { stocks, extend }; 212 }, 213}; 214let getSearchAutocomplete = (autoSelector) => { 215 let autoComp = { 216 _selector: autoSelector, 217 _listeners: {}, 218 listen(eventName, handler) { 219 if (!this._listeners[eventName]) { 220 this._listeners[eventName] = []; 221 } 222 this._listeners[eventName].push(handler); 223 }, 224 fire(eventName) { 225 let handlers = this._listeners[eventName]; 226 let args = Array.prototype.slice.call(arguments, 1); 227 if (!handlers) return; 228 handlers.forEach((handler) => { 229 handler(...args); 230 }); 231 }, 232 renderStocks: function (stocks) { 233 if (stocks.length === 1) { 234 let stock = stocks[0]; 235 let mrrStyle = 236 stock['mov_range'] > 0 237 ? 'float: right; color: #ef685f;' 238 : stock['mov_range'] < 0 239 ? 'float: right; color: #2ace43;' 240 : 'float: right;'; 241 return ` 242 <li class="auto__option pv2 ph3" data-value="${stock['stock_name']}" 243 data-strvalue="${stock['stock_name']}" data-numvalue="${stock['stock_num']}"> 244 <span class="text-gray-1 f5">${stock['stock_num']}</span> 245 <div> 246 <div class="dib v-mid text-gray-1" style="float: right;">${ 247 stock['country'] === 'TWO' ? '䏿«' : stock['country'] === 'emerging' ? 'èæ«' : 'ä¸å¸' 248 }</div> 249 <div class="text-gray-3 f4 fw9"> ${stock['stock_name']} </div> 250 <div class="text-gray-3 f5 fw9" style="${mrrStyle}"> ${Number(stock['mov_range']).toFixed(2) + '%'} </div> 251 <div class="text-gray-3 f5 fw9"> ${Number(stock['close']).toFixed(2)} </div> 252 </div> 253 </li> 254 `; 255 } else if (stocks.length > 1) { 256 return stocks.reduce( 257 (acc, stock) => 258 (acc += ` 259 <li class="auto__option text-gray-2 hover-gray-0" data-value="${stock['stock_name']}" 260 data-strvalue="${stock['stock_name']}" data-numvalue="${stock['stock_num']}"> 261 <div class="auto__stock dib pv2 ph3 fw6 v-mid">${stock['stock_num']} ${stock['stock_name']}</div> 262 <div class="dib v-mid text-gray-1">${
263 stock['country'] === 'TWO' ? '䏿«' : stock['country'] === 'emerging' ? 'èæ«' : 'ä¸å¸' 264 }</div> 265 </li> 266 `), 267 '' 268 ); 269 } 270 return ''; 271 }, 272 renderOptions: function (data, titleStr) { 273 let html = `<div class="text-primary-0 pv2 ph3">${titleStr}</div>`; 274 if (typeof data === 'string') { 275 html += `<li class="auto__option text-gray-2 hover-gray-0" data-value="${data}"> 276 <div class="pv2 ph3">${data}</div> 277 </li>`; 278 } 279 if (typeof data === 'object') { 280 html += data.reduce( 281 (acc, item) => 282 (acc += ` 283 <li class="auto__option text-gray-2 hover-gray-0" data-value="${item}"> 284 <div class="pv2 ph3">${item}</div> 285 </li> 286 `), 287 '' 288 ); 289 } 290 return html; 291 }, 292 handleKeyRender: function (query, { stocks, extend, history }) { 293 const { renderStocks, renderOptions } = this; 294 const autoEle = document.querySelector(autoSelector); 295 autoEle.innerHTML = renderOptions(history, 'æå°ç´é'); 296 }, 297 handleClickRender: function (history) { 298 const autoEle = document.querySelector(autoSelector); 299 if (autoEle.classList.contains('is-show')) return; 300 autoEle.classList.add('is-show'); 301 autoEle.innerHTML = searchRender.renderOptions(history, 'æå°ç´é'); 302 }, 303 select(ele) { 304 const autoEle = document.querySelector(autoSelector); 305 let selected = autoEle.querySelector('.is-selected'); 306 if (selected) { 307 selected.classList.remove('is-selected'); 308 } 309 ele.classList.add('is-selected'); 310 this.fire('onSelect', ele); 311 }, 312 selectNext() { 313 const autoEle = document.querySelector(autoSelector); 314 let options = autoEle.querySelectorAll('li.auto__option'); 315 let selected = autoEle.querySelector('.is-selected'); 316 let next = null; 317 if (selected) { 318 selected.classList.remove('is-selected'); 319 next = Array.prototype.filter.call(options, (e, i) => { 320 return options[i - 1] == selected; 321 })[0]; 322 } else { 323 next = options[0]; 324 } 325 if (next) { 326 next.classList.add('is-selected'); 327 } 328 }, 329 selectPrevious() { 330 const autoEle = document.querySelector(autoSelector); 331 let options = autoEle.querySelectorAll('li.auto__option'); 332 let selected = autoEle.querySelector('.is-selected'); 333 let previous = null; 334 if (selected) { 335 selected.classList.remove('is-selected'); 336 previous = Array.prototype.filter.call(options, (e, i) => { 337 return options[i + 1] == selected; 338 })[0]; 339 } else { 340 if (options[0]) { 341 previous = options[options.length - 1]; 342 } 343 } 344 if (previous) { 345 previous.classList.add('is-selected'); 346 } 347 }, 348 getSelectedValue() { 349 const autoEle = document.querySelector(autoSelector); 350 let selected = autoEle.querySelector('.is-selected'); 351 if (!selected) 352 return { 353 value: '', 354 strValue: '', 355 numValue: '', 356 }; 357 return { 358 value: selected.dataset.value, 359 strValue: selected.dataset.strvalue, 360 numValue: selected.dataset.numvalue, 361 }; 362 }, 363 initEvents() { 364 document.querySelector(autoSelector).addEventListener('click', (evt) => { 365 let option = evt.target.closest('.auto__option'); 366 if (option) { 367 window.location = `${$site_url}/isearch/?query=${option.dataset.value}`; 368 } 369 }); 370 document.querySelector(autoSelector).addEventListener('mouseover', (evt) => { 371 if (!evt.target) return; 372 let ele = evt.target.closest('li.auto__option'); 373 if (!ele) return; 374 this.select(ele); 375 }); 376 }, 377 }; 378 autoComp.initEvents(); 379 return autoComp; 380}; 381const searchRender = getSearchAutocomplete('.search__auto'); 382 383(async function initSearch() { 384 let search = (window.searchModuleInstance = searchModule()); 385 let searchData = search.getData();
386 let history = searchControls.getSearchHistory(); 387 try { 388 window.stockfeel.search = new Promise(async (resolve, reject) => { 389 const suggestData = await searchApis.getSearchSuggestData(window.template_url, window.site_url); 390 search.setSuggestData(suggestData); 391 392 const rankData = await searchApis.getSearchRank(); 393 search.setHotKeyword(rankData[0]['query']); 394 searchData = search.getData(); 395 search.setSuggestResult({ ...searchData.suggestResult, history }); 396 document.querySelector('.search__input').setAttribute('placeholder', searchData.hotKeyword); 397 398 resolve({ ...suggestData, rankData }); 399 reject({}); 400 }); 401 402 // searchControls.suggestDataReady(); 403 // if(suggestData.searchstock && window.location.pathname.match(/isearch/)) { 404 // const query = "<?php echo $_GET['query']; ?>".toLowerCase(); 405 // if(query) { 406 407 // window.stockfeel.search.setSuggestData(suggestData); 408 // // éé åªåè³æï¼ä¹å¾å render 409 // // let stock = window.stockfeel.search.getCurrentStockIfAny(); 410 // // if(stock) renderStock(stock.stock_num); 411 // } 412 // } 413 414 // document.querySelectorAll(".form-inline").forEach(node => { 415 // node.querySelector(".search-input-nav") 416 // .setAttribute("placeholder", hotkeywordData); 417 // node.querySelector(".search-input-hotkey").value = hotkeywordData; 418 // }); 419 } catch (err) { 420 console.log(err); 421 } 422 let getSearchKeyStrokes = (args) => { 423 let keyStrokes = { 424 datas: { 425 realInput: '', 426 selectedValue: '', 427 }, 428 _searchInputKeypress(evt) { 429 let query = evt.detail.target.value; 430 if (query != this.datas.realInput && query != this.datas.selectedValue) { 431 this.datas.realInput = query; 432 } 433 if (query) { 434 const { extend, stocks }
434 = searchControls.searchMatch(query, searchData.suggestData); 435 search.setSuggestResult({ 436 ...searchData.suggestResult, 437 extend, 438 stocks, 439 }); 440 if (evt.detail.key === 'Enter') { 441 window.location = `${$site_url}/isearch/?query=${query}`; 442 } 443 } else if (!query && evt.detail.key === 'Enter') { 444 window.location = `${$site_url}/isearch/?query=${searchData.hotKeyword}`; 445 } 446 args.autoEle.handleKeyRender(query, searchData.suggestResult); 447 }, 448 searchInputKeypress(evt) { 449 if (this.searchInputKeypressTimer) { 450 clearTimeout(this.searchInputKeypressTimer); 451 this.searchInputKeypressTimer = null; 452 } 453 this.searchInputKeypressTimer = setTimeout(() => { 454 this._searchInputKeypress(evt); 455 this.searchInputKeypressTimer = null; 456 }, 10); 457 }, 458 // å çºä¸ä¸éµä¸ç¼ keypress 459 searchInputKeydown(evt) { 460 if (evt.detail.key != 'ArrowUp' && evt.detail.key != 'ArrowDown') return; 461 if (evt.detail.key == 'ArrowUp') { 462 args.autoEle.selectPrevious(); 463 } 464 if (evt.detail.key == 'ArrowDown') { 465 args.autoEle.selectNext(); 466 } 467 this.updateSelectedValue(); 468 evt.detail.target.value = this.getDisplayValue(); 469 }, 470 updateSelectedValue() { 471 let selectedValue = args.autoEle.getSelectedValue(); 472 if ( 473 this.datas.realInput &&
474 searchControls.checkSearchType(this.datas.realInput) == 'NUM' && 475 selectedValue.numValue 476 ) { 477 this.datas.selectedValue = selectedValue.numValue; 478 } else { 479 this.datas.selectedValue = selectedValue.strValue || selectedValue.value; 480 } 481 }, 482 getDisplayValue() { 483 return this.datas.selectedValue || this.datas.realInput; 484 }, 485 }; 486 return keyStrokes; 487 }; 488 let headerSearchKeyStroke = getSearchKeyStrokes({ autoEle: searchRender }); 489 490 // header ç¶å¯¦éèçæ¹æ³ 491 document.querySelector('header').addEventListener('keypressInSearchInput', (evt) => { 492 headerSearchKeyStroke.searchInputKeypress(evt); 493 }); 494 document.querySelector('header').addEventListener('keydownInSearchInput', (evt) => { 495 headerSearchKeyStroke.searchInputKeydown(evt); 496 }); 497 searchRender.listen('onSelect', (e) => { 498 headerSearchKeyStroke.updateSelectedValue(); 499 // æå°é å¤ search bar 忥 500 [...document.querySelectorAll('.search__input')].forEach( 501 (ele) => (ele.value = headerSearchKeyStroke.getDisplayValue()) 502 ); 503 [...document.querySelectorAll('.search-and-member input')].forEach( 504 (ele) => (ele.value = headerSearchKeyStroke.getDisplayValue()) 505 ); 506 }); 507 // search auto complete 508 document.querySelector('input.search__input').addEventListener('click', (evt) => { 509 const { history } = searchData.suggestResult; 510 searchRender.handleClickRender(history); 511 headerSearchKeyStroke.searchInputKeypress({ 512 detail: { 513 target: { 514 value: evt.target.value, 515 }, 516 key: evt.key, 517 }, 518 }); 519 }); 520 document.querySelector('input.search__input').addEventListener('keypress', (evt) => { 521 window.searchModuleInstance.getEventHandler('header').keypress(evt); 522 }); 523 document.querySelector('input.search__input').addEventListener('keydown', (evt) => { 524 window.searchModuleInstance.getEventHandler('header').keydown(evt); 525 }); 526 if (document.querySelector('.header__search div img')) { 527 document.querySelector('.header__search div img').addEventListener('click', (evt) => { 528 let query = document.querySelector('input.search__input').value; 529 if (query) window.location = `${$site_url}/isearch/?query=${query}`; 530 }); 531 } 532})(); 533 534function getCookie(name) { 535 let nameEQ = name + '='; 536 let ca = document.cookie.split(';'); 537 for (let i = 0; i < ca.length; i++) { 538 let c = ca[i]; 539 while (c.charAt(0) == ' ') c = c.substring(1, c.length); 540 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); 541 } 542 return null; 543} 544 545// æå°é é¢çåå 546 547// //active header nav on current page 548// $('.nav-bottom-col.link').each(function() { 549// if (window.location.href.match($(this).prop('href') + '[a-zA-Z0-9./_]+')) { 550// $(this).toggleClass('actived'); 551// } 552// }); 553 554// $('.nav-bottom-col.menu').click(function() { 555// $(this).toggleClass('actived'); 556// }); 557 558// document.querySelectorAll(".form-inline .search-input-nav") 559// .forEach(node => { 560// node.addEventListener("keypress", (evt) => { 561// if(evt.keyCode === 13) { 562// const isSpacingEmpty = node.value.replace(/\s/g, '').length === 0 ? true : false; 563// if(isSpacingEmpty || node.value === "") { 564// const query = node.parentNode.querySelector(".search-input-hotkey").value; 565// node.value = query; 566// window.location.assign(`${$site_url}/isearch/?query=${query}`); 567// } else { 568// const query = node.value; 569// window.location.assign(`${$site_url}/isearch/?query=${query}`); 570// } 571// } 572// }) 573// }) 574 575// document.querySelectorAll(".form-inline button") 576// .forEach(node => { 577// node.addEventListener("click", (evt) => { 578// const isSpacingEmpty = node.value.replace(/\s/g, '').length === 0 ? true : false; 579// if(isSpacingEmpty || node.value === "") { 580// const query = node.parentNode.querySelector(".search-input-nav").value; 581// node.value = query; 582// window.location.assign(`${$site_url}/isearch/?query=${query}`); 583// } else { 584// const query = node.querySelector(".search-input-nav").value; 585// window.location.assign(`${$site_url}/isearch/?query=${query}`); 586// } 587// }) 588// }) 589 590// document.querySelectorAll(".form-inline").forEach(node => { 591// node.addEventListener("submit", (evt) => { 592// evt.preventDefault(); 593// }) 594// }) 595if (document.querySelector('.member__search-dummy')) { 596 document.querySelector('.member__search-dummy').classList.add('pointer'); 597 document.querySelector('.member__search-dummy').addEventListener('click', () => { 598 let inp = document.querySelector('.search-block input'); 599 if (inp && inp.value) { 600 window.location = `${$site_url}/isearch/?query=${inp.value}`; 601 } 602 }); 603}
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.