1$(function () { 2 var languageStorageKey = 'search-language-id'; 3 4 /** 5 * T-20260816-15/M1 (#1625 B): kullanici NAVIGASYON yolu aktif culture prefix'ini tasimali. 6 * PathBase sunucudan <body data-pathbase> ile gelir (TR'de bos string, /en'de "/en") -- 7 * basket.js/marketplace-redesign.js ile AYNI kopru. BULUNAN HATA: /en'de arama onerisine 8 * tiklayan kullanici prefix'siz /Card/Detail/... adresine gidiyordu, yani sessizce TR'ye 9 * dusuyordu. fetch()/XHR URL'lerine DOKUNULMADI. 10 */ 11 function pathBase() { 12 return (document.body && document.body.dataset && document.body.dataset.pathbase) || ''; 13 } 14 15 // Oneri satirlarinin hedef URL'i. Gercek <a href> uretebilmek icin tiklama 16 // aninda degil, render aninda hesaplanir; boylece orta tus / Ctrl+tik ile 17 // yeni sekmede acma, "Baglantiyi yeni sekmede ac" ve link onizleme calisir. 18 function buildResultHref(item, languageId) { 19 if (!item || !item.id) { 20 return ''; 21 } 22 23 var base = pathBase(); 24 25 if (item.type === 'card') { 26 return base + '/Card/Detail/' + encodeURIComponent(item.id); 27 } 28 29 if (item.type === 'set') { 30 return base + '/UserSet/Detail/' + encodeURIComponent(item.id); 31 } 32 33 if (item.type === 'illustrator') { 34 return base + '/Home/Search?searchTerm=' + encodeURIComponent(item.label || '') + 35 '&languageId=' + encodeURIComponent(languageId); 36 } 37 38 return ''; 39 } 40 41 // Combobox kalibi: <ul role="listbox"> icinde secilebilir oge role="option" 42 // olmali. Rolu <li>'ye degil <a>'ya veriyoruz â href korundugu icin orta tus / 43 // Ctrl+tik / "yeni sekmede ac" calismaya devam eder (bunlar tarayici 44 // seviyesinde href'e bagli, role'e degil), ekran okuyucu ise satiri combobox 45 // secenegi olarak duyurur. Odak input'ta kalir, aktif oge 46 // aria-activedescendant ile isaretlenir -> optionlar tabindex="-1". 47 function createResultItem(item, languageId, optionId) { 48 var $item = $('<li>') 49 .addClass('autocompleteItem') 50 .addClass('list-group-item list-group-item-action') 51 .attr('role', 'presentation') 52 .attr('data-id', item.id) 53 .attr('data-type', item.type); 54 55 var $link = $('<a>') 56 .addClass('ac-item-link') 57 .attr('id', optionId) 58 .attr('role', 'option') 59 .attr('aria-selected', 'false') 60 .attr('tabindex', '-1'); 61 62 var href = buildResultHref(item, languageId); 63 if (href) { 64 $link.attr('href', href); 65 } 66 67 var $content = $("<div class='ac-item-content'></div>"); 68 69 if (item.image) { 70 $content.append($("<div class='ac-card-img'><img src='" + item.image + "' width='50px'/></div>")); 71 } else { 72 var iconClass = item.type === 'illustrator' ? 'bi-brush' 73 : item.type === 'set' ? 'bi-collection' 74 : 'bi-image'; 75 $content.append($("<div class='ac-card-img ac-no-image'><i class='bi " + iconClass + " ac-icon' aria-hidden='true'></i></div>")); 76 } 77 78 var $desc = $("<div class='ac-card-desc'></div>") 79 .append( 80 $('<span>').text(item.label), 81 $('<span>').text(item.description) 82 ); 83 84 // Secenegin erisilebilir adi: "etiket, aciklama". Icerikten turetilse de 85 // acikca vermek, gorsel kutu/ikon gibi parcalarin ada karismasini onler. 86 var accessibleName = item.description 87 ? item.label + ', ' + item.description 88 : item.label; 89 $link.attr('aria-label', accessibleName); 90 91 $content.append($desc); 92 $link.append($content); 93 $item.append($link); 94 return $item; 95 } 96 97 function createCategoryHeader(categoryName, count) { 98 // Kategori basligi secilebilir bir oge degil; listbox'ta option olarak 99 // sayilmamasi icin role="presentation". 100 return $("<li class='autocompleteCategory list-group-item' role='presentation'></li>") 101 .append($('<strong>').text(categoryName + ' (' + count + ')')); 102 } 103 104 $('.search-box-wrap').each(function (wrapIndex) { 105 var $wrap = $(this); 106 107 // ---- i18n (Issue #1625 B, shared dilimi) ---------------------------- 108 // Kullaniciya gorunen metinler artik SABIT DEGIL: _SearchBoxPartial.cshtml 109 // .search-box-wrap kokune data-i18n-* basar, degerler _SearchBoxPartial.resx(.en) 110 // dosyalarindan gelir. Ayni desen basket.js/destek.js (#1613). Attribute yoksa 111 // bugunku TR metne duseriz -- davranis DEGISMEZ. 112 // ð´ Kok BU wrap'in KENDISI: arama kutusu layout'ta iki kez (masaustu + mobil) 113 // basiliyor, her ornek kendi attribute'larini okur. 114 function t(name, fallback) { 115 var value = $wrap.attr('data-i18n-' + name); 116 return (value === undefined || value === null || value === '') ? fallback : value; 117 } 118 /** '{0}' yer tutucusunu argumanlarla degistirir (basket.js ile ayni sozlesme). */ 119 function fmt(template) { 120 var args = Array.prototype.slice.call(arguments, 1); 121 return String(template == null ? '' : template).replace(/\{(\d+)\}/g, function (match, index) { 122 var v = args[Number(index)]; 123 return v === undefined ? match : v; 124 }); 125 } 126 127 var $input = $wrap.find('.search-term-input'); 128 var $list = $wrap.find('.autocomplete-list'); 129 var $languageMenu = $wrap.find('.search-language-menu'); 130 var $languageInput = $wrap.find('.search-language-id-input'); 131 var $flag = $wrap.find('.active-language-flag'); 132 var $globe = $wrap.find('.active-language-globe'); 133 var debounceTimer = null; 134 var pendingRequest = null; 135 var latestRequestId = 0;
136 var responseCache = new Map(); 137 138 // Arama kutusu layout'ta iki kez basiliyor (masaustu + mobil), bu yuzden 139 // id'ler ornek basina benzersiz olmali. 140 var listId = 'ac-list-' + wrapIndex; 141 var optionIdPrefix = 'ac-opt-' + wrapIndex + '-'; 142 var activeIndex = -1; 143 // Aktif secenegi klavye mi fare mi belirledi? Fare listeden ciktiginda 144 // sadece farenin koydugu vurgu temizlensin, klavye secimi ayakta kalsin. 145 var activeFromPointer = false; 146 147 $list.attr({ id: listId, role: 'listbox' }); 148 $input.attr({ 149 role: 'combobox', 150 'aria-expanded': 'false', 151 'aria-controls': listId, 152 'aria-autocomplete': 'list', 153 'aria-haspopup': 'listbox' 154 }); 155 156 // Ekran okuyucuya sonuc sayisini duyuran gorunmez bolge. 157 var $status = $('<div>') 158 .addClass('visually-hidden ac-status') 159 .attr({ role: 'status', 'aria-live': 'polite', 'aria-atomic': 'true' }) 160 .appendTo($wrap); 161 162 function announce(message) { 163 $status.text(message); 164 } 165 166 function $options() { 167 return $list.find('a.ac-item-link[role="option"]'); 168 } 169 170 function clearActiveOption() { 171 activeIndex = -1; 172 activeFromPointer = false; 173 $options().removeClass('ac-active').attr('aria-selected', 'false'); 174 $input.removeAttr('aria-activedescendant'); 175 } 176 177 function setActiveOption(index, fromPointer) { 178 var $all = $options(); 179 if (!$all.length) { 180 clearActiveOption(); 181 return; 182 } 183 184 // Uclarda basa/sona sar. 185 if (index < 0) { 186 index = $all.length - 1; 187 } else if (index >= $all.length) { 188 index = 0; 189 } 190 191 $all.removeClass('ac-active').attr('aria-selected', 'false'); 192 activeIndex = index; 193 activeFromPointer = !!fromPointer; 194 195 var $active = $all.eq(index) 196 .addClass('ac-active') 197 .attr('aria-selected', 'true'); 198 199 $input.attr('aria-activedescendant', $active.attr('id')); 200 201 // Aktif oge listede gorunur kalsin (klavyeyle asagi inerken). 202 // Fare ile gelen secimde kaydirma yapmayiz: zaten gorunur olan bir 203 // satirdir ve kaydirmak imlecin altindaki satiri degistirip geri 204 // besleme dongusu yaratir. 205 if (fromPointer) { 206 return; 207 } 208 209 var el = $active[0]; 210 if (el && typeof el.scrollIntoView === 'function') { 211 el.scrollIntoView({ block: 'nearest' }); 212 } 213 } 214 215 function showList() { 216 $list.show(); 217 $input.attr('aria-expanded', 'true'); 218 } 219 220 function hideList() { 221 $list.hide(); 222 $input.attr('aria-expanded', 'false'); 223 clearActiveOption(); 224 } 225 226 function getSelectedLanguageId() { 227 var value = Number($languageInput.val()); 228 if (!Number.isNaN(value) && value >= 0) { 229 return value; 230 } 231 return 0; 232 } 233 234 function abortPendingRequest() { 235 if (pendingRequest && pendingRequest.readyState !== 4) { 236 pendingRequest.abort(); 237 } 238 239 pendingRequest = null; 240 } 241 242 function renderResults(data) { 243 $list.empty(); 244 clearActiveOption(); 245 246 // GEÃİCİ BAKIM (T-20260715-03): sunucu autocomplete'i devre dıÅı bırakıp 247 // maintenance=true döndürünce, kullanıcıya boÅ açılır liste yerine kısa bilgi göster. 248 if (data && data.maintenance) { 249 var message = data.maintenanceMessage || t('maintenance', 'Arama önerileri geçici olarak bakımda'); 250 $list.append( 251 $("<li class='autocompleteCategory list-group-item text-muted' role='presentation'></li>").text(message) 252 ); 253 showList(); 254 announce(message); 255 return; 256 } 257 258 var totalResults = (data.cards ? data.cards.length : 0) + 259 (data.illustrators ? data.illustrators.length : 0) + 260 (data.sets ? data.sets.length : 0); 261 262 if (totalResults === 0) { 263 hideList(); 264 announce(t('no-results', 'Sonuç bulunamadı')); 265 return; 266 } 267 268 var languageId = getSelectedLanguageId(); 269 var optionIndex = 0; 270 271 function appendGroup(title, items) { 272 if (!items || items.length === 0) { 273 return; 274 } 275 276 $list.append(createCategoryHeader(title, items.length));
277 items.forEach(function (item) { 278 $list.append(createResultItem(item, languageId, optionIdPrefix + optionIndex)); 279 optionIndex++; 280 }); 281 } 282 283 appendGroup(t('group-cards', 'Kartlar'), data.cards); 284 appendGroup(t('group-illustrators', 'Illustratorler'), data.illustrators); 285 appendGroup(t('group-sets', 'Setler'), data.sets); 286 287 showList(); 288 announce(fmt(t('results-announcement', '{0} sonuç bulundu. Gezinmek için yön tuÅlarını kullanın.'), totalResults)); 289 } 290 291 function updateLanguageUi(id) { 292 if (Number(id) === 0) { 293 $flag.addClass('d-none'); 294 $globe.removeClass('d-none'); 295 return; 296 } 297 298 var $item = $languageMenu.find(".search-language-item[data-lang-id='" + id + "']").first(); 299 if (!$item.length) { 300 $flag.addClass('d-none'); 301 $globe.removeClass('d-none'); 302 return; 303 } 304 305 var code = ($item.data('flag-code') || 'gb').toString().toLowerCase(); 306 $flag.attr('src', 'https://flagcdn.com/w20/' + code + '.png'); 307 $flag.removeClass('d-none'); 308 $globe.addClass('d-none'); 309 } 310 311 function initializeLanguage() { 312 var selectedId = 0; 313 var stored = localStorage.getItem(languageStorageKey); 314 if (stored) { 315 var parsed = Number(stored); 316 if (!Number.isNaN(parsed) && parsed >= 0 && $languageMenu.find(".search-language-item[data-lang-id='" + parsed + "']").length > 0) { 317 selectedId = parsed; 318 } 319 } 320 321 $languageInput.val(selectedId); 322 updateLanguageUi(selectedId); 323 } 324 325 function runAutocomplete(term) { 326 var languageId = getSelectedLanguageId(); 327 var cacheKey = String(languageId) + '|' + term.toLowerCase(); 328 var requestId = ++latestRequestId; 329 330 if (responseCache.has(cacheKey)) { 331 renderResults(responseCache.get(cacheKey)); 332 return; 333 } 334 335 abortPendingRequest(); 336 337 pendingRequest = $.ajax({ 338 url: '/Home/Autocomplete', 339 method: 'GET', 340 data: { 341 term: term, 342 languageId: languageId 343 } 344 }) 345 .done(function (data) { 346 if (requestId !== latestRequestId) { 347 return; 348 } 349 350 responseCache.set(cacheKey, data); 351 renderResults(data); 352 }) 353 .fail(function (_xhr, status) { 354 if (status !== 'abort' && requestId === latestRequestId) { 355 hideList(); 356 } 357 }) 358 .always(function () { 359 if (requestId === latestRequestId) { 360 pendingRequest = null; 361 } 362 }); 363 } 364 365 initializeLanguage(); 366 367 $languageMenu.on('click', '.search-language-item', function () { 368 var selectedId = Number($(this).data('lang-id')); 369 if (Number.isNaN(selectedId) || selectedId < 0) { 370 return; 371 } 372 373 $languageInput.val(selectedId); 374 localStorage.setItem(languageStorageKey, String(selectedId)); 375 updateLanguageUi(selectedId); 376 responseCache.clear(); 377 378 if ($input.val().length >= 2) { 379 $input.trigger('input'); 380 } 381 }); 382 383 $input.on('input', function () { 384 var term = $input.val().trim(); 385 386 if (debounceTimer) { 387 clearTimeout(debounceTimer); 388 } 389 390 // Metin degisti: ekrandaki oneriler artik bayat. Vurguyu HEMEN 391 // dusuruyoruz, yoksa debounce (180ms) dolmadan Enter'a basildiginda 392 // eski listedeki secenegin href'ine gidilir â kullanicinin o an 393 // yazdigi terimle alakasiz bir sayfa. Vurgu yokken Enter serbest 394 // metin aramasina duser. 395 clearActiveOption(); 396 397 if (term.length < 2) { 398 abortPendingRequest(); 399 hideList(); 400 return; 401 } 402 403 debounceTimer = setTimeout(function () { 404 runAutocomplete(term); 405 }, 180); 406 }); 407 408 function runFreeTextSearch() { 409 var searchTerm = $input.val().trim(); 410 if (searchTerm.length >= 2) { 411 // ð´ QA (#1625 B AC4) BULGUSU: buradaki adres prefix'SIZ sabitti. Oneri 412 // satirlari buildResultHref() ile pathBase() aliyordu ama SERBEST METIN 413 // aramasi (vurgulu oneri yokken Enter / arama butonu) almiyordu -- /en'deki 414 // kullanici arama yaptigi anda sessizce TR arama sayfasina dusuyordu. 415 window.location.href = pathBase() + '/Home/Search?searchTerm=' + encodeURIComponent(searchTerm) + 416 '&languageId=' + encodeURIComponent(getSelectedLanguageId()); 417 } 418 } 419 420 // Klavye gezinmesi (WAI-ARIA combobox): odak input'ta kalir, aktif secenek 421 // aria-activedescendant ile isaretlenir. keypress ok tuslarini uretmedigi 422 // icin tum tuslar keydown'da ele alinir. 423 $input.on('keydown', function (e) { 424 // IME (Japonca/Korece aday secimi, Android GBoard) aday onaylarken de 425 // Enter uretir; eski keypress handler'i bunu gormezdi, keydown gorur. 426 // Onaylama Enter'ini arama tetikleyicisi saymayalim. 427 // DIKKAT: jQuery event nesnesi isComposing'i kopyalamaz (jQuery 3'un 428 // addProp listesinde yok) â originalEvent'ten okumak ZORUNLU, yoksa 429 // guard sessizce hicbir sey yapmaz. keyCode 229 Android GBoard yolu. 430 var nativeEvent = e.originalEvent || e; 431 if (nativeEvent.isComposing || e.which === 229 || e.keyCode === 229) { 432 return; 433 } 434 435 var key = e.key; 436 var listOpen = $list.is(':visible'); 437 var optionCount = $options().length; 438 439 if (key === 'ArrowDown' || key === 'ArrowUp') { 440 if (!listOpen) { 441 // Kapaliyken asagi ok, mevcut terim icin listeyi yeniden acar. 442 if ($input.val().trim().length >= 2) { 443 $input.trigger('input'); 444 } 445 return; 446 } 447 448 if (!optionCount) { 449 return; 450 } 451 452 e.preventDefault();
453 setActiveOption(activeIndex + (key === 'ArrowDown' ? 1 : -1)); 454 return; 455 } 456 457 if ((key === 'Home' || key === 'End') && listOpen && optionCount) { 458 e.preventDefault(); 459 setActiveOption(key === 'Home' ? 0 : optionCount - 1); 460 return; 461 } 462 463 if (key === 'Escape') { 464 if (listOpen) { 465 e.preventDefault(); 466 hideList(); 467 } 468 return; 469 } 470 471 if (key === 'Tab') { 472 // Odak kutudan cikiyorsa acik liste kalmasin. 473 if (listOpen) { 474 hideList(); 475 } 476 return; 477 } 478 479 if (key === 'Enter') { 480 e.preventDefault(); 481 482 var $active = listOpen && activeIndex >= 0 ? $options().eq(activeIndex) : $(); 483 var href = $active.length ? $active.attr('href') : ''; 484 485 if (href) { 486 hideList(); 487 window.location.href = href; 488 return; 489 } 490 491 // Vurgulu secenek yoksa eski davranis: serbest metin aramasi. 492 runFreeTextSearch(); 493 } 494 }); 495 496 // Fare ile gezinirken klavye vurgusu ile cift vurgu olusmasin. 497 // mouseenter DEGIL mousemove dinliyoruz: klavyeyle asagi inerken liste 498 // sabit duran imlecin altinda kayar ve mouseenter tetiklenir; bu, aktif 499 // secimi imlecin altindaki satira sicratip Enter'i yanlis karta goturur. 500 // mousemove yalnizca fare gercekten hareket ettiginde gelir. 501 $list.on('mousemove', 'a.ac-item-link[role="option"]', function () { 502 var index = $options().index(this); 503 if (index >= 0 && index !== activeIndex) {
504 setActiveOption(index, true); 505 } 506 }); 507 508 $list.on('mouseleave', function () { 509 // Klavyeyle secilmis bir secenek varsa fare listeden ciksa da kalsin. 510 if (activeFromPointer) { 511 clearActiveOption(); 512 } 513 }); 514 515 // Navigasyonu tarayici yapar (satirlar gercek <a href>). Burada sadece 516 // listeyi kapatiyoruz; yeni sekmede acan tiklamalarda (Ctrl/Cmd/Shift, 517 // orta tus -> 'click' hic tetiklenmez) liste acik kalsin ki kullanici 518 // ust uste birkac kart acabilsin. 519 $list.on('click', 'li.autocompleteItem a.ac-item-link', function (e) { 520 if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) { 521 return; 522 } 523 524 hideList(); 525 }); 526 527 // Disari tiklaninca kapat. hideList kullaniyoruz ki aria-expanded ve 528 // aria-activedescendant da sifirlansin. 529 $(document).on('click', function (e) { 530 if (!$(e.target).closest($list).length && !$(e.target).closest($input).length) { 531 if ($list.is(':visible')) { 532 hideList(); 533 } 534 } 535 }); 536 }); 537 538 $(document).on('submit', '.search-form', function () { 539 var $input = $(this).find('.search-language-id-input'); 540 if ($input.length && !$input.val()) { 541 $input.val('0'); 542 } 543 }); 544});
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.