1var aladin = aladin || {}; 2 3(function ($) { 4 aladin.search = { 5 _initialized: false, 6 7 init: function () { 8 if (this._initialized) return; 9 10 this.field = $(".search_field_area"); 11 this.form = this.field.find("#QuickSearch"); 12 this.baselayer = this.field.find("#Search_Layer"); 13 this.defaultlayer = this.field.find("#SearchDefaultLayer"); 14 this.completelayer = this.field.find("#SearchCompleteLayer"); 15 this.input = this.field.find("#SearchWord"); 16 this.submitBtn = this.field.find("#SearchSubmit"); 17 this.clearBtn = this.field.find("#search-clear-btn"); 18 this.isDirectDelivery = this.form.find("#IsDirectDelivery").val(); 19 20 //ê²ìì´, ì§íì¤ì¸ ìì² ìºì 21 this.cache = new Map(); 22 this.itemcache = new Map(); 23 this.requestCache = new Map(); 24 this.timer = null; 25 this.lastKeyword = ''; 26 27 // ìëìì± ë¤ë¹ê²ì´ì ë³ì 28 this.item_count = 0; 29 this.item_cursor_idx = -1; 30 31 this.searchTargetInit(); 32 this.searchBannerInit(); 33 this.searchLayerInit(); 34 this.searchInputInit(); 35 this.jpKeyboardInit(); 36 37 this._setupEventDelegation(); 38 this._initialized = true; 39 }, 40 41 // ìì ë° ì¤ì 42 maxCacheSize: 100, 43 debounceDelay: 180, 44 timeouts: { 45 search: 1000, //ìëìì±ê²ìAPI 46 keyword: 2000, //ìµê·¼ê²ìì´, ì¶ì²í¤ìë, ì§ê¸ë² ì¤í¸API 47 viewitem: 2000, //미리보기API 48 banner: 2000 //ë°°ëAPI 49 }, 50 51 /*###################### 52 Private ë©ìëë¤ 53 #######################*/ 54 55 _setupEventDelegation: function () { 56 $(document).on('click', '.open-popup-full', (e) => { 57 if (!$(e.target).parents(".search_field_area fieldset").length) { 58 $.publish("search-target.hide"); 59 $.publish("mode-search.hide"); 60 $.publish("jpKeyboard.hide"); 61 } 62 }); 63 }, 64 65 _manageCacheSize: function (cacheMap) { 66 while (cacheMap.size > this.maxCacheSize) { 67 const firstKey = cacheMap.keys().next().value; 68 cacheMap.delete(firstKey); 69 } 70 }, 71 72 _createElement: function (tag, attributes, content, isHTML = false) { 73 const element = document.createElement(tag); 74 75 if (attributes) { 76 Object.entries(attributes).forEach(([attr, value]) => { 77 element.setAttribute(attr, value); 78 }); 79 } 80 81 if (content) { 82 if (isHTML) { 83 element.innerHTML = this._sanitizeSearchTitle(content); 84 } else { 85 element.textContent = content; 86 } 87 } 88 89 return element; 90 }, 91 92 _sanitizeSearchTitle: function (title) { 93 return title 94 .replace(/<(?!\/?(em)\b)[^<>]*>/gi, '') 95 .replace(/</g, '<') 96 .replace(/>/g, '>') 97 .replace(/&/g, '&'); 98 }, 99 100 _deduplicateRequest: function (key, requestFn) { 101 if (this.requestCache.has(key)) { 102 return this.requestCache.get(key); 103 } 104 105 const promise = requestFn().finally(() => { 106 this.requestCache.delete(key); 107 }); 108 109 this.requestCache.set(key, promise); 110 return promise; 111 }, 112 113 _handleAjaxError: function (xhr, textStatus, error, context) { 114 const errorInfo = { 115 context, 116 status: xhr.status, 117 statusText: xhr.statusText, 118 error: error, 119 timestamp: Date.now(), 120 responseText: xhr.responseText || null 121 }; 122 123 const userMessages = { 124 0: 'ë¤í¸ìí¬ ì°ê²°ì íì¸í´ì£¼ì¸ì.', 125 404: 'ìì²í íì´ì§ë¥¼ ì°¾ì ì ììµëë¤.',
126 timeout: 'ìì² ìê°ì´ ì´ê³¼ëììµëë¤.', 127 default: 'ì¼ìì ì¸ ì¤ë¥ê° ë°ìíìµëë¤.' 128 }; 129 130 const message = xhr.status >= 500 ? 'ìë² ì ê² ì¤ì ëë¤.' : 131 userMessages[xhr.status] || 132 userMessages[textStatus] || 133 userMessages.default; 134 135 if (window.console && console.warn) { 136 console.warn( 137 `Ajax Error [${context}]`, 138 '\nUser Message:', message, 139 '\nError Details:', errorInfo, 140 '\nXHR Object:', xhr 141 ); 142 } 143 }, 144 145 _makeRequest: function (options) { 146 const defaults = { 147 type: "POST", 148 async: true, 149 cache: false, 150 dataType: "json", 151 timeout: this.timeouts[options.context] || 3000, 152 beforeSend: () => options.onStart?.(), 153 complete: () => options.onComplete?.(), 154 error: (xhr, textStatus, error) => { 155 this._handleAjaxError(xhr, textStatus, error, options.context); 156 options.onError?.(xhr, textStatus, error); 157 } 158 }; 159 160 // jQuery Ajax를 ë¤ì´í°ë¸ Promiseë¡ ëí 161 return new Promise((resolve, reject) => { 162 const jqXHR = $.ajax({ ...defaults, ...options }); 163 jqXHR.done(resolve).fail((xhr, textStatus, error) => { 164 // ìë¬ í¸ë¤ë¬ê° ì´ë¯¸ í¸ì¶ëìì¼ë¯ë¡ rejectë§ ìí 165 reject({ xhr, textStatus, error }); 166 }); 167 }); 168 }, 169 170 _saveRecentData: function (type, value) { 171 const params = new URLSearchParams({ method: 'add' }); 172 173 if (type === 'keyword') { 174 params.set('keyword', encodeURIComponent(value)); 175 } else if (type === 'item') { 176 params.set('itemid', value); 177 } else { 178 console.warn('Invalid save type:', type); 179 return; 180 } 181 182 if (navigator.sendBeacon) { 183 navigator.sendBeacon("/search/v2/recent-keyword.ashx", params); 184 } else { 185 this._makeRequest({ 186 url: "/search/v2/recent-keyword.ashx", 187 data: Object.fromEntries(params.entries()), 188 timeout: 1500, 189 context: `saveRecent${type.charAt(0).toUpperCase()}${type.slice(1)}` 190 }).catch(() => { }); 191 } 192 }, 193 194 /*###################### 195 ê²ì ë°°ë ì´ê¸°í 196 #######################*/ 197 searchBannerInit: function () { 198 const $input = this.input; 199 const $submit = this.submitBtn; 200 const branchtype = this.form.data("branchtype"); 201 202 if ($input.val().length > 0 || this.form.data('banner-active') == 0) { 203 return; 204 } 205 206 this._makeRequest({ 207 url: "/banner/search-banner.ashx", 208 data: { branchtype: branchtype }, 209 timeout: this.timeouts.banner, 210 context: 'searchBanner' 211 }).then(data => { 212 if (data?.ResultCode == 1 && data.banner) { 213 $input.attr('placeholder', data.banner.AdText).addClass("ad"); 214 215 const redirect = '/banner/wadbanner_redirect.aspx?PubBannerId=' + data.banner.PubBannerId; 216 $submit.off("click.banner").on("click.banner", (e) => { 217 e.preventDefault(); 218 location.href = redirect; 219 }); 220 } 221 }).catch(error => { 222 console.warn('Banner loading failed:', error); 223 }); 224 }, 225 226 /*###################### 227 ì¼ë³¸ì´ í¤ë³´ë ì´ê¸°í 228 #######################*/ 229 jpKeyboardInit: function () { 230 const $jpBtn = this.form.find(".jp_word_btn"); 231 const $jpLayer = this.form.find(".jp_word_layer_positon"); 232 const $jpSearchWord = $jpLayer.find("#JP-SearchWord"); 233 const $jpSubmit = $jpLayer.find(".jp_word_submit");
234 const $jpClose = $jpLayer.find(".layer_close"); 235 236 $.subscribe("jpKeyboard.hide", () => { 237 $jpLayer.fullpop("hide"); 238 }); 239 240 $jpBtn.on("click", (e) => { 241 e.preventDefault(); 242 if ($jpLayer.is(":visible")) { 243 $jpLayer.fullpop("hide"); 244 } else { 245 $.publish("mode-search.hide"); 246 $jpLayer.fullpop("show"); 247 this.defaultlayer.hide(); 248 } 249 }); 250 251 $jpLayer.find(".jp_word_table td a").on("click", (e) => { 252 e.preventDefault(); 253 const val = $(e.currentTarget).text(); 254 if (val.length > 0) { 255 $jpSearchWord.val($jpSearchWord.val() + val); 256 } 257 }); 258 259 $jpSubmit.on("click", (e) => { 260 e.preventDefault(); 261 const jsword = $jpSearchWord.val(); 262 const length = jsword.length; 263 264 $jpSearchWord.val(""); 265 this.input.val(jsword); 266 $jpLayer.fullpop("hide"); 267 268 setTimeout(() => { 269 this.input[0].focus(); 270 this.input[0].setSelectionRange(length, length); 271 }, 100); 272 }); 273 274 $jpClose.on("click", (e) => { 275 e.preventDefault(); 276 $jpLayer.fullpop("hide"); 277 }); 278 }, 279 280 /*###################### 281 ê²ì íê² ì´ê¸°í 282 #######################*/ 283 searchTargetInit: function () { 284 const $target = this.form.find("#Search-Target-Desc"); 285 const $targetList = this.form.find("#Search-Target-List"); 286 const isUsedStore = this.form.find("input[name='KeyTag']").length > 0; 287 const $jpBtn = this.form.find(".jp_word_btn"); 288 const $searchTarget = this.form.find("input[name='SearchTarget']"); 289 290 $.subscribe("search-target.show", () => { 291 if ($('body').hasClass('mode-search')) { 292 $.publish("mode-search.hide"); 293 } 294 this.form.find("#Search-Target-List").fullpop("show"); 295 $target.addClass("active"); 296 }); 297 298 $.subscribe("search-target.hide", () => { 299 this.form.find("#Search-Target-List").fullpop("hide"); 300 $target.removeClass("active"); 301 }); 302 303 $target.off("click.searchtarget keypress.searchtarget") 304 .on("click.searchtarget keypress.searchtarget", (e) => { 305 if (e.type === "click" || (e.type === "keypress" && e.which === 13)) { 306 e.preventDefault(); 307 if (e.originalEvent.pointerType === "mouse" || e.which === 13) { 308 $targetList.is(":visible") ? 309 $.publish("search-target.hide") : 310 $.publish("search-target.show"); 311 } 312 } 313 }); 314 315 $targetList.find("button").off("click.searchtarget keypress.searchtarget") 316 .on("click.searchtarget keypress.searchtarget", (e) => { 317 e.preventDefault(); 318 $targetList.fullpop("hide"); 319 320 const element = $(e.currentTarget); 321 $target.text(element.text()); 322 323 if (isUsedStore) { 324 this.form.find("input[name='KeyTag']").val(element.data('target')); 325 } else { 326 $target.attr('data-target', element.data('target')); 327 this.searchTargetChangeSet(element.data('target')); 328 this.form.find("input[name='SearchTarget']").val(element.data('target')); 329 330 if (element.data('target') === 'Foreign') { 331 $jpBtn.show(); 332 this.clearBtn.addClass("jp"); 333 } else { 334 $jpBtn.hide(); 335 this.clearBtn.removeClass("jp"); 336 } 337 } 338 339 // ìºì ì´ê¸°í 340 this.cache?.clear(); 341 342 setTimeout(() => { 343 this.input[0].focus(); 344 }, 200); 345 }); 346 347 if ($searchTarget.val() === 'Foreign') { 348 $jpBtn.show(); 349 this.clearBtn.addClass("jp"); 350 } 351 352 if ($target.length > 0) { 353 this.searchTargetChangeSet($target.data('target')); 354 } else { 355 this.searchTargetChangeSet($searchTarget.val()); 356 } 357 }, 358 359 searchTargetChangeSet: function (target) { 360 const mode = this.form.attr('data-site-mode'); 361 362 if (target === "UsedStore" || target === "Talktalk") { 363 if (target === "UsedStore" && this.isDirectDelivery == 2) { 364 this.form.attr('data-active', 1); 365 } else { 366 this.form.attr('data-active', 0); 367 } 368 this.input.attr('data-autocomplete-active', 0); 369 } 370 else if (target === "Coffee") { 371 this.form.attr('data-active', 1); 372 this.input.attr('data-autocomplete-active', 0); 373 } 374 else if (target === "Used" && this.form.find("input[name='Ke
374yTag']").length > 0) { 375 this.form.attr('data-active', 0); 376 this.input.attr('data-autocomplete-active', 0); 377 } 378 else if (target === undefined && mode == 1) { 379 this.form.attr('data-active', 0); 380 this.input.attr('data-autocomplete-active', 0); 381 } 382 else { 383 this.form.attr('data-active', 1); 384 this.input.attr('data-autocomplete-active', 1); 385 } 386 }, 387 388 /*###################### 389 ê²ì ì ë ¥ ì´ê¸°í 390 #######################*/ 391 searchInputInit: function () { 392 let debounceTimer = null; 393 394 const debouncedSearch = (keyword, searchTarget, mode) => { 395 clearTimeout(debounceTimer); 396 debounceTimer = setTimeout(() => { 397 this._performSearch(keyword, searchTarget, mode); 398 }, this.debounceDelay); 399 }; 400 401 $(document).off("keydown.input keyup.input focusin.input mousedown.input propertychange.input input.input") 402 .on("keydown.input keyup.input focusin.input mousedown.input propertychange.input input.input", "#SearchWord", (e) => { 403 const keyword = this.input.val(); 404 405 if (e.type === 'mousedown') { 406 if (window.getSelection) { 407 window.getSelection().removeAllRanges(); 408 } 409 if (document.selection) { 410 document.selection.empty(); 411 } 412 } 413 414 // keydownììë§ ì²ë¦¬í ì´ë²¤í¸ë¤ 415 if (e.type === 'keydown') { 416 // ìëìì± ë¤ë¹ê²ì´ì 417 if (e.which === 40 || e.which === 38) { 418 e.preventDefault(); 419 if (this.item_count === 0 || !this.completelayer.is(":visible")) { 420 return; 421 } 422 this._handleAutoCompleteNavigation(e); 423 return; 424 } 425 426 // ìí°í¤ ì²ë¦¬ 427 if (e.which === 13) { 428 e.preventDefault(); 429 if (this.item_cursor_idx > -1) { 430 const $autoComplete = this.completelayer.find(".auto_complete"); 431 const itemid = $autoComplete.find("li").eq(this.item_cursor_idx).data("itemid"); 432 this.viewItem(itemid, true); 433 } else { 434 this.searchResult(keyword, true); 435 } 436 return; 437 } 438 } 439 440 // ê²ìì´ ì ë ¥ ì²ë¦¬ 441 if (e.type === 'input' || e.type === 'keyup' || e.type === 'focusin' || e.type === 'propertychange' || e.type === 'mousedown') { 442 if (e.which === 40 || e.keyCode === 40 || e.which === 38 || e.keyCode === 38 || e.which === 13 || e.keyCode === 13) { 443 return; 444 } 445 this._handleSearchInput(keyword, debouncedSearch); 446 } 447 }); 448 }, 449 450 _handleAutoCompleteNavigation: function (e) { 451 const $auto_complete = this.completelayer.find(".auto_complete"); 452 453 if (e.which === 40) { // Down key 454 this.item_cursor_idx = this.item_cursor_idx < this.item_count - 1 ? 455 this.item_cursor_idx + 1 : 0; 456 } else if (e.which === 38) { // Up key 457 this.item_cursor_idx = this.item_cursor_idx > 0 ? 458 this.item_cursor_idx - 1 : -1; 459 } 460 461 if (this.item_cursor_idx === -1) { 462 $auto_complete.find("li").removeClass("on"); 463 } else { 464 const $obj = $auto_complete.find("li").eq(this.item_cursor_idx); 465 if ($obj.length) { 466 $obj.addClass("on").siblings().removeClass("on"); 467 const itemid = $obj.data("itemid"); 468 if (itemid) { 469 this.searchItemPreview(itemid, this.item_cursor_idx); 470 } 471 } 472 } 473 }, 474 475 _handleSearchInput: function (keyword, debouncedSearch) { 476 // ì´ì í¤ìëì ëì¼í ê²½ì° ì¤íµ 477 if (keyword === this.lastKeyword && keyword.length > 0) { 478 return; 479 } 480 this.lastKeyword = keyword; 481 482 const active = this.input.attr('data-autocomplete-active'); 483 const mode = this.form.attr('data-site-mode'); 484 const searchTarget = this.form.find("input[name='SearchTarget']").val(); 485 486 this.clearBtn.css('visibility', keyword ? 'visible' : 'hidden'); 487 488 if (!$('body').hasClass('mode-search')) { 489 this.input.attr('placeholder', ''); 490 $.publish("mode-submit-btn.normal"); 491 if (this.form.attr('data-active') == 1) { 492 $.publish("mode-search.show"); 493 } 494 } 495 496 this.item_cursor_idx = -1; 497 498 if (active == 0) { 499 keyword ? this.defaultlayer.hide() : this.defaultlayer.show(); 500 return; 501 } 502 503 if (!keyword) { 504 const $autoComplete = this.completelayer.find(".auto_complete"); 505 const $productInfo = this.completelayer.find(".product_info"); 506 $autoComplete.empty(); 507 $productInfo.empty(); 508 this.completelayer.hide(); 509 this.defaultlayer.show(); 510 } else { 511 if (this.defaultlayer.is(":visible")) { 512 setTimeout(() => this.defaultlayer.hide(), 300); 513 } 514 debouncedSearch(keyword, searchTarget, mode); 515 } 516 }, 517 518 /*###################### 519 ê²ì ìí ë¡ì§ 520 #######################*/ 521 _performSearch: function (keyword, searchTarget, mode) { 522 const $autoComplete = this.completelayer.find(".auto_complete"); 523 const $completelayer = this.completelayer; 524 525 // ìºì íì¸ 526 if (this.cache.has(keyword)) { 527 const cachedHtml = this.cache.get(keyword); 528 $autoComplete.html(cachedHtml); 529 $completelayer.show(); 530 531 const $firstItem = $autoComplete.find("li").eq(0); 532 if ($firstItem.length) { 533 this.searchItemPreview($firstItem.data("itemid")); 534 } 535 return Promise.resolve(); 536 } 537 538 const requestKey = `search_${keyword}`; 539 return this._deduplicateRequest(requestKey, () => {
540 return this._makeRequest({ 541 url: "/search/v2/search-items.ashx", 542 data: { 543 q: encodeURIComponent(keyword), 544 t: searchTarget, 545 mode: mode 546 }, 547 timeout: this.timeouts.search, 548 context: 'performSearch' 549 }); 550 }).then(data => { 551 if (data?.ResultCode === 1 && Array.isArray(data.List)) { 552 this.item_count = data.List.length; 553 if (data.List.length === 0) { 554 $completelayer.hide(); 555 return; 556 } 557 558 const html = this._buildAutoCompleteHTML(data.List); 559 $autoComplete.html(html); 560 $completelayer.show(); 561 562 // ìºì ì ì¥ 563 this._manageCacheSize(this.cache); 564 this.cache.set(keyword, html); 565 566 if (data.List[0]?.ItemId) { 567 this.searchItemPreview(data.List[0].ItemId); 568 } 569 } else if (data?.ResultCode === 0) { 570 this.input.attr('data-autocomplete-active', 0); 571 $completelayer.hide(); 572 if (this.form.attr('data-active') == 1) { 573 this.defaultlayer.show(); 574 } 575 } 576 }).catch(error => { 577 //console.warn('Search request failed:', error); 578 $completelayer.hide(); 579 }); 580 }, 581 582 _buildAutoCompleteHTML: function (items) { 583 const ul = this._createElement('ul'); 584 const fragment = document.createDocumentFragment(); 585 586 items.forEach(item => { 587 if (item?.ItemId && item?.Title) { 588 const li = this._createElement('li', { 'data-itemid': item.ItemId }); 589 const a = this._createElement('a', { 590 'href': `javascript:aladin.search.viewItem(${item.ItemId}, true);`, 591 'onfocus': `aladin.search.searchItemPreview(${item.ItemId});`, 592 'onmouseover': `aladin.search.searchItemPreview(${item.ItemId});` 593 }); 594 595 a.innerHTML = this._sanitizeSearchTitle(item.Title); 596 li.appendChild(a); 597 fragment.appendChild(li); 598 } 599 }); 600 601 ul.appendChild(fragment); 602 return ul.outerHTML; 603 }, 604 605 /*###################### 606 ìì´í 미리보기 607 #######################*/ 608 searchItemPreview: function (itemid, cursor_idx) { 609 if (!itemid) return; 610 611 const $productInfo = this.completelayer.find(".product_info"); 612 const $autoComplete = this.completelayer.find(".auto_complete"); 613 614 if (cursor_idx === undefined) { 615 $autoComplete.find("li").removeClass("on"); 616 this.item_cursor_idx = -1; 617 } 618 619 if (this.itemcache.has(itemid)) { 620 $productInfo.html(this.itemcache.get(itemid)); 621 return; 622 } 623 624 $productInfo.empty(); 625 626 this._makeRequest({ 627 url: "/search/v2/search-item-preview.ashx", 628 data: { itemid: itemid }, 629 timeout: this.timeouts.viewitem, 630 context: 'searchItemPreview' 631 }).then(data => { 632 if (data?.ResultCode == 1) { 633 const html = this._buildPreviewHTML(data, itemid); 634 $productInfo.html(html); 635 636 // ìºì ì ì¥ 637 this._manageCacheSize(this.itemcache); 638 this.itemcache.set(itemid, html); 639 } 640 }).catch(error => { 641 console.warn('Preview request failed:', error); 642 }); 643 }, 644 645 _buildPreviewHTML: function (data, itemid) { 646 const url = `javascript:aladin.search.viewItem(${itemid}, true);`; 647 let html = `<div class="cover"><a href="${url}"><img src="${data.Cover}"></a>`; 648 649 if (data.IsBasketable) { 650 html += `<button type="button" class="add_basket" onclick="aladin.search.addBasket(${data.ItemId},event)">ì¥ë°êµ¬ë ë´ê¸°</button>`; 651 } 652 html += '</div>'; 653 654 html += `<div class="txt"><div class="book_tit"><a href="${url}">${data.Title}</a></div>`; 655 html += '<div class="book_info">'; 656 657 if (data.Author?.length > 0) html += `<span>${data.Author}</span>`;
658 if (data.Publisher?.length > 0) html += `<span>${data.Publisher}</span>`; 659 if (data.PublishDate?.length > 0) html += `<span>${data.PublishDate}</span>`; 660 661 html += `</div><div class="book_price">${data.Price}</div></div>`; 662 663 return html; 664 }, 665 666 /*###################### 667 ê²ì ë ì´ì´ ì´ê¸°í 668 #######################*/ 669 searchLayerInit: function () { 670 this._setupSearchLayerEvents(); 671 this._setupSearchSubscriptions(); 672 this._setupInputEvents(); 673 }, 674 675 _setupSearchLayerEvents: function () { 676 $.subscribe("mode-search.show", () => { 677 $("body").addClass("mode-search"); 678 this.baselayer.fullpop("show"); 679 $.publish("search-target.hide"); 680 681 // ë¹ë기 ë°ì´í° ë¡ë© 682 Promise.allSettled([ 683 this._loadRecentKeywords(), 684 this._loadRecommendKeywords(), 685 this._loadNowBest(), 686 //this._loadRecentViewItems() 687 ]); 688 }); 689 690 $.subscribe("mode-search.hide", () => { 691 this.baselayer.fullpop('hide'); 692 this.item_cursor_idx = -1; 693 $.publish("mode-autocomplet.clear"); 694 setTimeout(() => $("body").removeClass("mode-search"), 100); 695 }); 696 697 $.subscribe("mode-autocomplet.clear", () => { 698 const $completelayer = this.completelayer; 699 $completelayer.find(".auto_complete").html(""); 700 $completelayer.find(".product_info").html(""); 701 $completelayer.hide(); 702 this.lastKeyword = ""; 703 }); 704 705 $.subscribe("mode-submit-btn.normal", () => { 706 this.submitBtn.off("click").on("click", (e) => { 707 e.preventDefault(); 708 this.searchResult(this.input.val(), true); 709 }); 710 }); 711 }, 712 713 _setupInputEvents: function () { 714 this.input.on("click", (e) => { 715 e.preventDefault(); 716 if ($('body').hasClass('mode-search')) return; 717 718 $(e.currentTarget).attr('placeholder', '').removeClass("ad"); 719 $.publish("mode-submit-btn.normal"); 720 721 if (this.form.attr('data-active') == 1) { 722 $.publish("mode-search.show"); 723 if (this.form.find(".jp_word_layer_positon").is(":visible")) { 724 $.publish("jpKeyboard.hide"); 725 } 726 } else { 727 $.publish("search-target.hide"); 728 } 729 }); 730 731 this.clearBtn.on("click", (e) => { 732 e.preventDefault(); 733 this.input.val(''); 734 setTimeout(() => this.input[0].focus(), 100); 735 }); 736 737 this.form.find(".layer_close").on("click", (e) => { 738 e.preventDefault(); 739 $.publish("mode-search.hide"); 740 }); 741 742 if (this.input.val().length > 0) { 743 this.clearBtn.css('visibility', 'visible'); 744 } 745 }, 746 747 /*###################### 748 ìµê·¼ ê²ìì´ ë¡ë© 749 #######################*/ 750 _loadRecentKeywords: function () { 751 const $section = $("#recent-keyword-section"); 752 if ($section.hasClass("on")) return Promise.resolve(); 753 754 $section.empty(); 755 756 return this._makeRequest({ 757 url: "/search/v2/recent-keyword.ashx", 758 timeout: this.timeouts.keyword, 759 context: 'recent_keyword_bind' 760 }).then(data => { 761 if (data?.ResultCode == 1 && Array.isArray(data.List)) { 762 this._renderRecentKeywords($section, data); 763 } else { 764 this._renderEmptyRecentKeywords($section); 765 } 766 }).catch(error => { 767 console.warn('Failed to load recent keywords:', error); 768 }); 769 }, 770 771 _renderRecentKeywords: function ($section, data) { 772 const items = data.List.map(item => { 773 const fn = item.ItemId > 0 774 ? `aladin.search.viewItem(${item.ItemId}, undefined, 'pcsearch_recen');` 775 : `aladin.search.changeKeyword('${encodeURIComponent(item.Keyword)}')`; 776 777 return `<li><a href="javascript:${fn}">${item.Keyword}</a> 778 <button type="button" class="del_btn" onclick="aladin.search.deleteRecentKeyword('delete', ${item.Seq}, event)">ìì </button></li>`; 779 }).join(''); 780 781 const html = ` 782 <div class="tit"> 783 <h3 tabindex="0">ìµê·¼ ê²ìì´</h3> 784 <button type="button" class="all_del" onclick="aladin.search.deleteRecentKeyword('delete_all',0,event)">ì ì²´ìì </button> 785 </div> 786 <ul>${items}</ul>`; 787 788 $section.append(html).show().addClass("on"); 789 }, 790 791 _renderEmptyRecentKeywords: function ($section) { 792 const html = ` 793 <div class="tit"><h3 tabindex="0">ìµê·¼ ê²ìì´</h3></div> 794 <div class="none_box"><p>ìµê·¼ ê²ìì´ê° ììµëë¤.</p></div>`; 795 $section.append(html).show().addClass("on"); 796 }, 797 798 /*###################### 799 ì¶ì² í¤ìë ë¡ë© 800 #######################*/ 801 _loadRecommendKeywords: function () { 802 const $section = $("#recommand-keyword-section"); 803 if ($section.hasClass("on")) return Promise.resolve(); 804 805 const mode = this.form.attr('data-site-mode'); 806
807 return this._makeRequest({ 808 url: "/search/v2/recommand-keyword.ashx", 809 data: { mode: mode }, 810 timeout: this.timeouts.keyword, 811 context: 'recommand_keyword_bind' 812 }).then(data => { 813 if (data?.ResultCode == 1 && Array.isArray(data.List)) { 814 const keywords = data.List.map(item => { 815 const url = item.Link + (item.Link.includes("?") ? "&" : "?") + "start=pcsearch_mdkey"; 816 return `<a href="${url}">${item.Keyword}</a>`; 817 }).join(''); 818 819 const html = ` 820 <div class="tit"><h3 tabindex="0">ì¶ì² í¤ìë</h3></div> 821 <div class="keyword_box">${keywords}</div>`; 822 823 $section.append(html).show().addClass("on"); 824 } 825 }).catch(error => { 826 console.warn('Failed to load recommend keywords:', error); 827 }); 828 }, 829 830 /*###################### 831 ì§ê¸ ë² ì¤í¸ ë¡ë© 832 #######################*/ 833 _loadNowBest: function () { 834 const $section = $("#now-best-section"); 835 if ($section.hasClass("on")) return Promise.resolve(); 836 837 const mode = this.form.attr('data-site-mode'); 838 839 return this._makeRequest({ 840 url: "/search/v2/now-best.ashx", 841 data: { mode: mode }, 842 timeout: this.timeouts.keyword, 843 context: 'now_best_bind' 844 }).then(data => { 845 if (data?.ResultCode == 1 && Array.isArray(data.List)) { 846 this._renderNowBest($section, data); 847 } 848 }).catch(error => { 849 console.warn('Failed to load now best:', error); 850 }); 851 }, 852 853 _renderNowBest: function ($section, data) { 854 let items = ""; 855 856 for (let i = 0; i < data.List.length; i++) { 857 if (i % 5 === 0) { 858 if (i > 0) items += '</ol></div>'; 859 items += '<div class="swiper-slide"><ol>'; 860 } 861 862 const item = data.List[i]; 863 items += `<li><a href="/shop/wproduct.aspx?ItemId=${item.ItemId}&start=pcsearch_beitem"> 864 <div class="cover"><img src="${item.Cover}"/></div> 865 <div class="txt"><em>${item.Rank}.</em> ${item.Title}</div> 866 </a></li>`; 867 868 if (i === data.List.length - 1) { 869 items += '</ol></div>'; 870 } 871 } 872 873 const header = ` 874 <div class="tit"> 875 <h3><a href="/shop/common/wbest.aspx?BestType=NowBest&start=pcsearch_beitem">ì§ê¸ ë² ì¤í¸</a></h3> 876 <div> 877 <div class="time" tabindex="0">${data.CalcTime}</div> 878 <div class="now_best_btn"> 879 <button type="button" class="now_best-prev" tabindex="-1" title="1ì~5ì ëª©ë¡ ë³´ê¸°">ì´ì </button> 880 <button type="button" tabindex="-1" class="now_best-next" title="6ì~10ì ëª©ë¡ ë³´ê¸°">ë¤ì</button> 881 </div> 882 </div> 883 </div>`; 884 885 const swiperHtml = `<div class="swiper now_best_list"><div class="swiper-wrapper">${items}</div></div>`; 886 887 $section.append(header + swiperHtml).show().addClass("on"); 888
889 // Swiper ì´ê¸°í 890 requestAnimationFrame(() => { 891 this._initNowBestSwiper(); 892 }); 893 }, 894 895 _initNowBestSwiper: function () { 896 try { 897 new Swiper(".now_best_list", { 898 slidesPerView: 1, 899 observer: true, 900 observeParents: true, 901 navigation: { 902 nextEl: ".now_best_btn .now_best-next", 903 prevEl: ".now_best_btn .now_best-prev", 904 }, 905 touchRatio: 0, 906 allowTouchMove: false, 907 on: { 908 afterInit: function () { 909 $('.now_best-next, .now_best-prev').attr('tabindex', '-1'); 910 }, 911 slideChange: function () { 912 $('.now_best-next, .now_best-prev').attr('tabindex', '-1'); 913 }, 914 }, 915 }); 916 } catch (error) { 917 // Swiper ë¼ì´ë¸ë¬ë¦¬ê° ìë ê²½ì° ëì ë¡ë 918 $.getScript('/ucl_editor/js/swiper.min.js', () => { 919 this._initNowBestSwiper(); 920 }); 921 } 922 }, 923 924 /*###################### 925 ìµê·¼ 본 ìí ë¡ë© (구ë ë°©ì ì ì§) 926 #######################*/ 927 _loadRecentViewItems: function () { 928 return new Promise((resolve) => { 929 $.publish("recent-viewitem.bind"); 930 resolve(); 931 }); 932 }, 933 934 _setupSearchSubscriptions: function () { 935 // ìµê·¼ 본 ìí 구ë (기존 ë¡ì§ ì ì§) 936 $.subscribe("recent-viewitem.bind", function () { 937 const $section = $("#recent-viewitem-section"); 938 if ($section.hasClass("on")) return; 939 940 aladin.search._makeRequest({ 941 url: "/search/v2/recent-viewitem.ashx", 942 timeout: aladin.search.timeouts.viewitem, 943 context: 'recent_viewitem_bind' 944 }).then(data => { 945 if (data?.ResultCode == 1 && Array.isArray(data.List)) { 946 // ìµê·¼ 본 ìí ë ëë§ ë¡ì§... 947 $section.addClass("on"); 948 } 949 }).catch(error => { 950 console.warn('Failed to load recent view items:', error); 951 }); 952 }); 953 }, 954 955 /*###################### 956 ê²ì ê²°ê³¼ ì²ë¦¬ 957 #######################*/ 958 searchResult: function (keyword, needsave) { 959 keyword = keyword.trim(); 960 961 if (!keyword) { 962 alert('ê²ìì´ë¥¼ ì ë ¥íì¸ì.'); 963 this.input.focus(); 964 return; 965 } 966 967 if (needsave) { 968 this._saveRecentData('keyword', keyword); 969 } 970 971 this.form.submit(); 972 }, 973 974 /*###################### 975 ìì´í ê´ë ¨ ë©ìë 976 #######################*/ 977 addBasket: function (itemid, e) { 978 e.preventDefault(); 979 if (itemid) { 980 location.href = '/shop/wbasket.aspx?addbook=' + itemid + '&start=slayer'; 981 } 982 }, 983 984 viewItem: function (itemid, needsave, param) { 985 if (!itemid) return false; 986 987 param = param || "pcsearch_auto"; 988 989 if (needsave === undefined) { 990 this._navigateToItem(itemid, param); 991 } else { 992 this._saveRecentData('item', Math.abs(itemid)); 993 this._navigateToItem(itemid, param); 994 } 995 996 return false; 997 }, 998 999 _navigateToItem: function (itemid, param) { 1000 const url = itemid < 0 1001 ? `/shop/usedshop/wuseditemall.aspx?ItemId=${Math.abs(itemid)}&start=${param}` 1002 : `/shop/wproduct.aspx?ItemId=${itemid}&start=${param}`; 1003 location.href = url; 1004 }, 1005 1006 /*###################### 1007 í¤ìë ê´ë ¨ ë©ìë 1008 #######################*/ 1009 changeKeyword: function (keyword) { 1010 this.input.val(decodeURIComponent(keyword)); 1011 this.form.append('<input type="hidden" name="start" value="pcsearch_recen" />'); 1012 this.searchResult(decodeURIComponent(keyword)); 1013 }, 1014 1015 deleteRecentKeyword: function (method, seq, e) { 1016 e.preventDefault(); 1017
1018 return this._makeRequest({ 1019 url: "/search/v2/recent-keyword.ashx", 1020 data: { method: method, Seq: seq }, 1021 timeout: this.timeouts.keyword, 1022 context: 'deleteRecentKeyword' 1023 }).then(data => { 1024 if (data?.ResultCode == 1) { 1025 $("#recent-keyword-section").removeClass("on"); 1026 return this._loadRecentKeywords(); 1027 } 1028 }).catch(error => { 1029 console.warn('Failed to delete recent keyword:', error); 1030 }); 1031 }, 1032 1033 /*###################### 1034 ì í¸ë¦¬í° ë©ìëë¤ 1035 #######################*/ 1036 searchLayerClose: function () { 1037 this.input.blur(); 1038 $.publish("mode-search.hide"); 1039 }, 1040 }; 1041})(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.