1/** 2 * IONO Items JS 3 * Version: 2.2.0 4 * 5 * - åå表示ã¯PHP 6 * - åææ¡ä»¶ã¯ data-initial-json ã§ä¿æ 7 * - ãã©ã¼ã ã«ãªãåææ¡ä»¶ãæ¶ããªã 8 * - ãã©ã¼ã ã§è§¦ã£ãé ç®ã ã activeParams ã«ä¸æ¸ã 9 * - more: Ajaxã§ãã£ã¨è¦ã 10 * - pager: URLãã©ã¡ã¼ã¿ä»ããªã³ã¯ã§ãã¼ã¸ç§»å 11 * - æ¤ç´¢å¾URLãæ¸ãæãã¦å ±æå¯è½ã«ãã 12 * - æ»ããã¿ã³ã§HTML/ã¹ã¯ãã¼ã«/currentPage/activeParams復å 13 */ 14 15(function () { 16 'use strict'; 17 18 const DEFAULT_API = 'https://ojiya-chijimi.jp/wp-json/iono/v1/items'; 19 const DEBUG = false; 20 21 const STATE_KEY = 'iono_items_state_v20'; 22 const STATE_EXPIRE = 30 * 60 * 1000; 23 24 let currentPage = 1; 25 let isLoading = false; 26 let searchTimer = null; 27 28 let initialParams = {}; 29 let activeParams = {}; 30 let dirtyFields = {}; 31 32 // ç¾å¨DOMã«è¡¨ç¤ºããã¦ããæ¡ä»¶ã®URLãpushState/popstateå¤å®ã«ä½¿ãã 33 let renderedUrl = location.href; 34 35 if ('scrollRestoration' in history) { 36 history.scrollRestoration = 'manual'; 37 } 38 39 function debug(message, data) { 40 if (!DEBUG) return; 41 42 let box = document.getElementById('ionoDebugBox'); 43 44 if (!box) { 45 box = document.createElement('div'); 46 box.id = 'ionoDebugBox'; 47 box.style.cssText = [ 48 'margin:15px 0', 49 'padding:12px', 50 'border:2px solid #d00', 51 'background:#fff5f5', 52 'color:#111', 53 'font-size:13px', 54 'line-height:1.5', 55 'white-space:pre-wrap', 56 'overflow:auto', 57 'max-height:500px', 58 'z-index:99999' 59 ].join(';'); 60 61 const searchBox = document.getElementById('ionoSearchBox'); 62 63 if (searchBox) { 64 searchBox.insertAdjacentElement('afterend', box); 65 } else { 66 document.body.prepend(box); 67 } 68 } 69 70 box.textContent = 71 '[' + new Date().toLocaleTimeString() + '] ' + message + '\n\n' + 72 (data !== undefined ? JSON.stringify(data, null, 2) : ''); 73 } 74 75 function esc(str) { 76 return String(str ?? '').replace(/[&<>"']/g, function (m) { 77 return { 78 '&': '&', 79 '<': '<', 80 '>': '>', 81 '"': '"', 82 "'": ''' 83 }[m]; 84 }); 85 } 86 87 function clone(obj) { 88 return JSON.parse(JSON.stringify(obj || {})); 89 } 90 91 const PUBLIC_TO_INTERNAL = { 92 icat: 'cat', 93 str: 'keyword', 94 icolor: 'colors', 95 pmin: 'min_price', 96 pmax: 'max_price', 97 psize: 'limit', 98 npage: 'paged', 99 iclass: 'class_name', 100 class_name: 'class_name', 101 exclude_codes: 'exclude_codes', 102 tax: 'tax_mode', 103 taxrate: 'tax_rate', 104 fields: 'display_fields' 105 }; 106 107 const INTERNAL_TO_PUBLIC = { 108 cat: 'icat', 109 keyword: 'str', 110 colors: 'icolor', 111 min_price: 'pmin', 112 max_price: 'pmax', 113 orderby: 'orderby', 114 limit: 'psize', 115 tag: 'tag', 116 class_name: 'iclass', 117 exclude_codes: 'exclude_codes', 118 tax_mode: 'tax', 119 tax_rate: 'taxrate', 120 display_fields: 'fields' 121 }; 122 123 function publicNameToInternal(name) { 124 return PUBLIC_TO_INTERNAL[name] || name; 125 } 126 127 function normalizeOrderby(value) { 128 value = String(value || '').trim(); 129 if (value === 'sales' || value === 'sales_order' || value === '人æ°é ' || value === '人æ°é ' || value === '人æ°é ') return 'sales_amount'; 130 return value; 131 } 132 133 function publicOrderby(value) { 134 value = normalizeOrderby(value); 135 if (value === 'sales_amount') return 'sales'; 136 return value; 137 } 138 139 140 function normalizeParams(params) { 141 const out = {}; 142 143 Object.keys(params || {}).forEach(function (key) { 144 const internalKey = publicNameToInternal(key); 145 out[internalKey] = params[key]; 146 }); 147 148 if (out.category && !out.cat) out.cat = out.category; 149 if (out.s && !out.keyword) out.keyword = out.s; 150 if (out.orderby) out.orderby = normalizeOrderby(out.orderby); 151 152 return out; 153 } 154 155 function isSalesOrder(params) { 156 params = normalizeParams(params || {}); 157 return ['sales_amount', 'sales_qty'].includes(normalizeOrderby(params.orderby || '')); 158 } 159 160 function isSalesRankingFirstPage(params) { 161 params = normalizeParams(params || {}); 162 const page = parseInt(params.paged || params.npage || 1, 10) || 1; 163 return isSalesOrder(params) && page === 1; 164 } 165 166 function getSalesRankStart(params) { 167 params = normalizeParams(params || {}); 168 169 if (!isSalesOrder(params)) return 0; 170 171 const page = parseInt(params.paged || params.npage || 1, 10) || 1; 172 if (page <= 1) return 1; 173 174 const limit = Math.max(1, parseInt(params.limit || params.psize || 24, 10) || 24); 175 return 11 + ((page - 2) * limit); 176 } 177 178 function renderRankingItems(items) { 179 if (!Array.isArray(items) || !items.length) { 180 return '<div class="iono-items-empty">ååãè¦ã¤ããã¾ããã§ããã</div>'; 181 } 182 183 return [ 184 '<section class="iono-ranking-section" aria-label="人æ°ååã©ã³ãã³ã°">', 185 '<div class="iono-ranking-heading">', 186 '<span class="iono-ranking-heading-sub">BEST SELLERS</span>', 187 '<div class="iono-ranking-title">人æ°ã©ã³ãã³ã° TOP10</div>', 188 '</div>', 189 '<div class="iono-ranking-list">', 190 items.map(function (item, index) { 191 const rank = index + 1; 192 const mainImage = item.ranking_image || item.image || ''; 193 const subImage = item.ranking_hover_image || ''; 194 const hasSubImage = !!subImage && subImage !== mainImage; 195 196 let priceHtml = ''; 197 if (item.price_label) { 198 if (item.is_sale && item.regular_price_label) { 199 priceHtml = '<div class="iono-item-price iono-item-price-sale">' + 200 '<span class="iono-item-regular-price">' + esc(item.regular_price_label) + '</span>' + 201 '<span class="iono-item-sale-price">' + esc(item.price_label) + '</span>' + 202 '</div>'; 203 } else { 204 priceHtml = '<div class="iono-item-price">' + esc(item.price_label) + '</div>'; 205 } 206 } 207 208 return [ 209 '<a class="iono-ranking-card iono-ranking-rank-' + rank + '" href="' + esc(item.url) + '">', 210 '<div class="iono-ranking-rank" aria-label="人æ°ã©ã³ãã³ã°ç¬¬' + rank + 'ä½">', 211 '<span class="iono-ranking-rank-number">' + rank + '</span>', 212 '<span class="iono-ranking-rank-unit">ä½</span>', 213 '</div>', 214 '<div class="iono-ranking-images ' + (hasSubImage ? 'has-sub-image' : 'is-single-image') + '">', 215 mainImage ? '<div class="iono-ranking-image iono-ranking-image-main"><img src="' + esc(mainImage) + '" alt="' + esc(item.name) + '" loading="' + (rank === 1 ? 'eager' : 'lazy') + '"></div>' : '', 216 hasSubImage ? '<div class="iono-ranking-image iono-ranking-image-sub"><img src="' + esc(subImage) + '" alt="' + esc(item.name) + ' å¥ç»å" loading="lazy"></div>' : '', 217 '</div>', 218 '<div class="iono-ranking-body">', 219 '<div class="iono-ranking-label">人æ°ã©ã³ãã³ã° 第' + rank + 'ä½</div>', 220 item.code ? '<div class="iono-item-code">' + esc(item.code) + '</div>' : '', 221 '<div class="iono-ranking-name">' + esc(item.name) + '</div>', 222 priceHtml, 223 Array.isArray(item.display_fields) && item.display_fields.length ? '<div class="iono-ranking-meta-list">' + item.display_fields.map(function (field) { 224 return '<div class="iono-item-meta iono-item-meta-' + esc(field.key || '') + '">' + esc(field.label || '') + 'ï¼' + esc(field.value || '') + '</div>'; 225 }).join('') + '</div>' : '', 226 '<span class="iono-ranking-detail">åå詳細ãè¦ã</span>', 227 '</div>', 228 '</a>' 229 ].join(''); 230 }).join(''), 231 '</div>', 232 '</section>' 233 ].join(''); 234 } 235 236 function renderItems(items, params) { 237 if (!Array.isArray(items) || !items.length) { 238 return '<div class="iono-items-empty">ååãè¦ã¤ããã¾ããã§ããã</div>'; 239 } 240 241 if (isSalesRankingFirstPage(params)) { 242 return renderRankingItems(items); 243 } 244 245 const columns = Math.max(1, parseInt((params && params.columns) || 4, 10) || 4); 246 const rankStart = getSalesRankStart(params); 247 const showNormalRanks = rankStart > 10; 248 249 return [ 250 '<div class="iono-items-grid iono-cols-' + columns + (showNormalRanks ? ' iono-items-ranked-grid' : '') + '">', 251 items.map(function (item, index) { 252 const rank = showNormalRanks ? rankStart + index : 0; 253 254 return [ 255 '<a class="iono-item-card' + (rank ? ' iono-item-card-ranked' : '') + '" href="' + esc(item.url) + '">', 256 rank ? '<div class="iono-item-rank-badge" aria-label="人æ°ã©ã³ãã³ã°ç¬¬' + rank + 'ä½"><span class="iono-item-rank-number">' + rank + '</span><span class="iono-item-rank-unit">ä½</span></div>' : '', 257 '<div class="iono-item-image">', 258 item.image ? [ 259 '<img', 260 ' src="' + esc(item.image) + '"', 261 ' data-main="' + esc(item.image) + '"', 262 ' data-hover="' + esc(item.hover_image || item.image) + '"', 263 ' alt="' + esc(item.name) + '"', 264 ' loading="lazy"', 265 '>' 266 ].join('') : '', 267 '</div>', 268 '<div class="iono-item-body">', 269 item.code ? '<div class="iono-item-code">' + esc(item.code) + '</div>' : '', 270 '<div class="iono-item-name">' + esc(item.name) + '</div>', 271 item.price_label ? ( 272 item.is_sale && item.regular_price_label 273 ? '<div class="iono-item-price iono-item-price-sale">' + 274 '<span class="iono-item-regular-price">' + esc(item.regular_price_label) + '</span>' + 275 '<span class="iono-item-sale-price">' + esc(item.price_label) + '</span>' + 276 '</div>' 277 : '<div class="iono-item-price">' + esc(item.price_label) + '</div>' 278 ) : '', 279 Array.isArray(item.display_fields) ? item.display_fields.map(function (field) { 280 return '<div class="iono-item-meta iono-item-meta-' + esc(field.key || '') + '">' + esc(field.label || '') + 'ï¼' + esc(field.value || '') + '</div>'; 281 }).join('') : '', 282 '</div>', 283 '</a>' 284 ].join(''); 285 }).join(''), 286 '</div>' 287 ].join(''); 288 } 289 290 function bindHover(root) { 291 if (!root) return; 292 293 root.querySelectorAll('img[data-hover]').forEach(function (img) { 294 img.onmouseenter = function () { 295 if (img.dataset.hover) { 296 img.src = img.dataset.hover; 297 } 298 }; 299 300 img.onmouseleave = function () { 301 if (img.dataset.main) { 302 img.src = img.dataset.main; 303 } 304 }; 305 }); 306 } 307 308 function resetHoverImages(root) { 309 if (!root) return; 310 311 root.querySelectorAll('img[data-main]').forEach(function (img) { 312 if (img.dataset.main) { 313 img.src = img.dataset.main; 314 } 315 }); 316 } 317 318 function parseInitialParams(box) { 319 let params = {}; 320 321 if (box.dataset.initialJson) {
322 try { 323 params = JSON.parse(box.dataset.initialJson); 324 } catch (e) { 325 debug('data-initial-json ãJSONã¨ãã¦èªãã¾ãã', { 326 initialJson: box.dataset.initialJson, 327 error: e.message 328 }); 329 params = {}; 330 } 331 } 332 333 delete params.return; 334 delete params.debug_html; 335 336 if (!params.orderby) params.orderby = 'recommend'; 337 if (!params.order) params.order = 'DESC'; 338 if (!params.limit) params.limit = 24; 339 if (!params.columns) params.columns = 4; 340 if (!params.format) params.format = 'html'; 341 if (!params.tax_mode) params.tax_mode = 'tax_in'; 342 343 return normalizeParams(params); 344 } 345 346 function getFormState(box) { 347 const state = {}; 348 349 if (!box) return state; 350 351 box.querySelectorAll('input, select, textarea').forEach(function (el) { 352 if (!el.name) return; 353 354 if (el.type === 'checkbox') { 355 if (!state[el.name]) state[el.name] = []; 356 if (el.checked) state[el.name].push(el.value); 357 return; 358 } 359 360 if (el.type === 'radio') { 361 if (el.checked) state[el.name] = el.value; 362 return; 363 } 364 365 state[el.name] = publicNameToInternal(el.name) === 'orderby' ? publicOrderby(el.value) : el.value; 366 }); 367 368 return state; 369 } 370 371 function restoreFormState(box, formState) { 372 if (!box || !formState) return; 373 374 box.querySelectorAll('input, select, textarea').forEach(function (el) { 375 if (!el.name) return; 376 377 let value = formState[el.name]; 378 379 if (value === undefined) return; 380 if (publicNameToInternal(el.name) === 'orderby') value = publicOrderby(value); 381 382 if (el.type === 'checkbox') { 383 el.checked = Array.isArray(value) 384 ? value.includes(el.value) 385 : String(value) === String(el.value); 386 return; 387 } 388 389 if (el.type === 'radio') { 390 el.checked = String(value) === String(el.value); 391 return; 392 } 393 394 el.value = value; 395 }); 396 } 397 398 function getFieldValue(box, name) { 399 const publicName = name; 400 const internalName = publicNameToInternal(name); 401 let els = Array.from(box.querySelectorAll('[name="' + publicName + '"]')); 402 403 if (!els.length && publicName !== internalName) { 404 els = Array.from(box.querySelectorAll('[name="' + internalName + '"]')); 405 } 406 407 if (!els.length) return ''; 408 409 const first = els[0]; 410 411 if (first.type === 'checkbox') { 412 return els 413 .filter(function (el) { 414 return el.checked; 415 }) 416 .map(function (el) { 417 return el.value; 418 }) 419 .join(','); 420 } 421 422 if (first.type === 'radio') { 423 const checked = els.find(function (el) { 424 return el.checked; 425 }); 426 427 return checked ? checked.value : ''; 428 } 429 430 return first.value; 431 } 432 433 function markFieldDirtyAndUpdate(box, name) { 434 if (!name) return; 435 436 const internalName = publicNameToInternal(name); 437 438 dirtyFields[internalName] = true; 439 440 const value = getFieldValue(box, name); 441 442 /** 443 * 触ã£ãé ç®ã ã䏿¸ãã 444 * ãã©ã¼ã ã«åå¨ããªãåææ¡ä»¶ã¯ activeParams ã«æ®ãã 445 */ 446 activeParams[internalName] = internalName === 'orderby' ? normalizeOrderby(value) : value; 447 448 if (!activeParams.orderby) activeParams.orderby = 'recommend'; 449 if (!activeParams.order) activeParams.order = 'DESC'; 450 if (!activeParams.limit) activeParams.limit = 24; 451 if (!activeParams.columns) activeParams.columns = 4; 452 if (!activeParams.format) activeParams.format = 'html'; 453 if (!activeParams.tax_mode) activeParams.tax_mode = 'tax_in'; 454 } 455 456 function buildRequestParams() { 457 const params = normalizeParams(clone(activeParams)); 458 459 params.paged = currentPage; 460 params.format = params.format || 'html'; 461 462 delete params.return; 463 delete params.debug_html; 464 465 return params; 466 } 467 468 function cleanParamsForUrl(params) { 469 const clean = {}; 470 const normalized = normalizeParams(params || {}); 471 472 Object.keys(INTERNAL_TO_PUBLIC).forEach(function (key) { 473 let val = normalized[key]; 474 if (val === undefined || val === null || val === '') return; 475 476 // ç¨è¾¼10%ã¯ããã©ã«ãæ±ããªã®ã§URLã«ã¯åºããªãã 477 if (key === 'tax_mode') { 478 const tv = String(val).trim(); 479 if (tv === '' || tv === 'tax_in') return; 480 } 481 if (key === 'tax_rate') { 482 const rv = String(val).trim(); 483 const rn = parseFloat(rv); 484 if (rv === '' || rv === '0.10' || rv === '0.1' || rv === '10' || Math.abs(rn - 0.10) < 0.000001 || Math.abs(rn - 10) < 0.000001) return; 485 } 486 487 if (key === 'orderby') val = publicOrderby(val); 488 clean[INTERNAL_TO_PUBLIC[key]] = val; 489 }); 490 491 return clean; 492 } 493 494 function buildListUrl(params, pageNo, navMode) { 495 const url = new URL(location.href); 496 497 const removeKeys = [ 498 'icat', 'str', 'icolor', 'pmin', 'pmax', 'orderby', 'psize', 'npage', 'tag', 'iclass', 'class_name', 'exclude_codes', 'tax', 'taxrate', 'fields', 499 'cat', 'category', 'codes', 'exclude_codes', 'keyword', 'colors', 'materials', 500 'min_price', 'max_price', 'order', 'limit', 'columns', 501 'include_desc', 'page_no', 'paged', 'nav_mode', 502 'iono_cat', 'iono_category', 'iono_codes', 'iono_exclude_codes', 'iono_tag', 'iono_keyword', 503 'iono_colors', 'iono_materials', 'iono_min_price', 'iono_max_price', 504 'iono_orderby', 'iono_order', 'iono_limit', 'iono_columns', 505 'iono_include_desc', 'iono_page', 'iono_nav' 506 ]; 507 508 removeKeys.forEach(function (key) { 509 url.searchParams.delete(key); 510 }); 511 512 const clean = cleanParamsForUrl(params); 513
514 Object.keys(clean).forEach(function (key) { 515 url.searchParams.set(key, clean[key]); 516 }); 517 518 url.searchParams.set('npage', String(pageNo || 1)); 519 520 // ç¨è¾¼10%ã¯å é¨ããã©ã«ãæ±ãããã¼ã¸ã£ã¼/URLåæã§ã¯çµ¶å¯¾ã«åºããªãã 521 ['tax', 'taxrate', 'tax_mode', 'tax_rate'].forEach(function (key) { 522 url.searchParams.delete(key); 523 }); 524 525 return url.toString(); 526 } 527 528 529 530 function getPagerElements() { 531 const pagers = Array.from(document.querySelectorAll('.ionoPager')); 532 533 /** 534 * æ§HTMLäºæãid=ionoPager ãæ®ã£ã¦ãã¦ãåãããã«ããã 535 */ 536 const oldPager = document.getElementById('ionoPager'); 537 if (oldPager && !pagers.includes(oldPager)) { 538 pagers.push(oldPager); 539 } 540 541 return pagers; 542 } 543 544 function clearPagers() { 545 getPagerElements().forEach(function (pager) { 546 pager.innerHTML = ''; 547 }); 548 } 549 550 const IONO_HISTORY_PARAM_KEYS = [ 551 'icat', 'str', 'icolor', 'pmin', 'pmax', 'orderby', 'psize', 'npage', 552 'tag', 'iclass', 'class_name', 'exclude_codes', 'tax', 'taxrate', 'fields', 553 'cat', 'category', 'codes', 'keyword', 'colors', 'materials', 554 'min_price', 'max_price', 'order', 'limit', 'columns', 555 'include_desc', 'page_no', 'paged', 'nav_mode' 556 ]; 557 558 function hasIonoListUrlParams(urlValue) { 559 let url; 560 561 try { 562 url = urlValue instanceof URL 563 ? urlValue 564 : new URL(urlValue || location.href, location.href); 565 } catch (e) { 566 return false; 567 } 568 569 return Array.from(url.searchParams.keys()).some(function (key) { 570 return key.indexOf('iono_') === 0 || IONO_HISTORY_PARAM_KEYS.includes(key); 571 }); 572 } 573 574 function mergedHistoryState(extra) { 575 const current = history.state && typeof history.state === 'object' 576 ? history.state 577 : {}; 578 579 return Object.assign({}, current, extra || {}); 580 } 581 582 /** 583 * ãã©ã¡ã¼ã¿ã¼ãªãã«ãã´ãªãå±¥æ´ã®åºç¹ã¨ãã¦å°ãä»ããã 584 * URLãç´æ¥éããå ´åã¯ããã®URLããã£ã«ã¿ã¼æ¸ã¿ã®å±¥æ´ã¨ãã¦æ±ãã 585 */ 586 function initializeIonoHistoryEntry() { 587 if (!history || !history.replaceState) return; 588 589 renderedUrl = location.href; 590 591 const hasParams = hasIonoListUrlParams(location.href); 592 const current = history.state && typeof history.state === 'object' 593 ? history.state 594 : {}; 595 596 if (hasParams) { 597 if (!current.ionoItemsFiltered) { 598 history.replaceState(mergedHistoryState({ 599 ionoItemsFiltered: true, 600 ionoItemsBase: false, 601 ionoItemsHasBase: false 602 }), '', location.href); 603 } 604 return; 605 } 606 607 if (!current.ionoItemsBase) { 608 history.replaceState(mergedHistoryState({ 609 ionoItemsBase: true, 610 ionoItemsFiltered: false, 611 ionoItemsHasBase: false 612 }), '', location.href); 613 } 614 } 615 616 /** 617 * æåã®æ¡ä»¶å¤æ´ã ãpushStateãã以å¾ã®å¤æ´ã¯replaceStateããã 618 * ããã«ãããæ»ãã1åã§ãã©ã¡ã¼ã¿ã¼ãªãã«ãã´ãªã¸æ»ããã 619 */ 620 function updateUrl(params, pageNo, navMode) { 621 if (!history || !history.replaceState) return; 622 623 const newUrl = buildListUrl(params, pageNo, navMode); 624 625 if (newUrl === location.href) { 626 renderedUrl = newUrl; 627 return; 628 } 629 630 const current = history.state && typeof history.state === 'object' 631 ? history.state 632 : {}; 633 634 const shouldPush = !!history.pushState && 635 !hasIonoListUrlParams(location.href) && 636 !current.ionoItemsFiltered; 637 638 const nextState = mergedHistoryState({ 639 ionoItemsFiltered: true, 640 ionoItemsBase: false, 641 ionoItemsHasBase: shouldPush || !!current.ionoItemsHasBase 642 }); 643 644 if (shouldPush) { 645 history.pushState(nextState, '', newUrl); 646 } else { 647 history.replaceState(nextState, '', newUrl); 648 } 649 650 renderedUrl = newUrl; 651 } 652 653 function clearIonoUrlParams() { 654 if (!history || !history.replaceState) return; 655 656 const url = new URL(location.href); 657 Array.from(url.searchParams.keys()).forEach(function (key) { 658 if (key.indexOf('iono_') === 0 || IONO_HISTORY_PARAM_KEYS.includes(key)) { 659 url.searchParams.delete(key); 660 } 661 }); 662 663 const cleanUrl = url.toString(); 664 665 history.replaceState(mergedHistoryState({ 666 ionoItemsBase: true, 667 ionoItemsFiltered: false, 668 ionoItemsHasBase: false 669 }), '', cleanUrl); 670 671 renderedUrl = cleanUrl; 672 } 673 674 function renderPager(params, pageNo, data, navMode) { 675 const pagers = getPagerElements(); 676 if (!pagers.length) return; 677 678 const totalPages = data && data.max_pages 679 ? parseInt(data.max_pages, 10) 680 : 0; 681 682 if (!totalPages || totalPages <= 1) { 683 clearPagers(); 684 return; 685 } 686 687 pageNo = parseInt(pageNo || 1, 10) || 1; 688 689 let html = '<div class="iono-pager">'; 690 691 if (pageNo > 1) {
692 html += '<a class="iono-page-link iono-page-prev" href="' + esc(buildListUrl(params, pageNo - 1, navMode)) + '">åã¸</a>'; 693 } 694 695 const maxLinks = 9; 696 let start = Math.max(1, pageNo - 4); 697 let end = Math.min(totalPages, start + maxLinks - 1); 698 699 if (end - start + 1 < maxLinks) { 700 start = Math.max(1, end - maxLinks + 1); 701 } 702 703 if (start > 1) { 704 html += '<a class="iono-page-link" href="' + esc(buildListUrl(params, 1, navMode)) + '">1</a>'; 705 if (start > 2) { 706 html += '<span class="iono-page-dots">...</span>'; 707 } 708 } 709 710 for (let i = start; i <= end; i++) { 711 if (i === pageNo) { 712 html += '<span class="iono-page-current">' + i + '</span>'; 713 } else { 714 html += '<a class="iono-page-link" href="' + esc(buildListUrl(params, i, navMode)) + '">' + i + '</a>'; 715 } 716 } 717 718 if (end < totalPages) { 719 if (end < totalPages - 1) { 720 html += '<span class="iono-page-dots">...</span>'; 721 } 722 723 html += '<a class="iono-page-link" href="' + esc(buildListUrl(params, totalPages, navMode)) + '">' + totalPages + '</a>'; 724 } 725 726 if (pageNo < totalPages) { 727 html += '<a class="iono-page-link iono-page-next" href="' + esc(buildListUrl(params, pageNo + 1, navMode)) + '">次ã¸</a>'; 728 } 729 730 html += '</div>'; 731 732 pagers.forEach(function (pager) { 733 pager.innerHTML = html; 734 }); 735 } 736 737 function savePageState(reason) { 738 const box = document.getElementById('ionoSearchBox'); 739 const result = document.getElementById('ionoSearchResult'); 740 741 if (!box || !result) return; 742 743 resetHoverImages(result); 744 745 const state = { 746 pageUrl: location.href.split('#')[0], 747 time: Date.now(), 748 reason: reason || '', 749 scrollY: window.scrollY || window.pageYOffset || 0, 750 resultHtml: result.innerHTML, 751 formState: getFormState(box), 752 currentPage: currentPage, 753 initialParams: initialParams, 754 activeParams: activeParams, 755 dirtyFields: dirtyFields 756 }; 757 758 try { 759 sessionStorage.setItem(STATE_KEY, JSON.stringify(state)); 760 761 debug('ãã¼ã¸ç¶æ ãä¿åãã¾ãã', { 762 reason: reason, 763 currentPage: currentPage, 764 scrollY: state.scrollY, 765 activeParams: activeParams, 766 dirtyFields: dirtyFields, 767 htmlLength: state.resultHtml.length 768 }); 769 } catch (e) { 770 debug('ãã¼ã¸ç¶æ ã®ä¿åã«å¤±æãã¾ãã', { 771 message: e.message 772 }); 773 } 774 } 775 776 function loadPageState() { 777 try { 778 const raw = sessionStorage.getItem(STATE_KEY); 779 if (!raw) return null; 780 781 const state = JSON.parse(raw); 782 783 if (!state.time || Date.now() - state.time > STATE_EXPIRE) { 784 sessionStorage.removeItem(STATE_KEY); 785 return null; 786 } 787 788 if (state.pageUrl !== location.href.split('#')[0]) { 789 return null; 790 } 791 792 return state; 793 } catch (e) { 794 return null; 795 } 796 } 797 798 function isBackForward(event) { 799 if (event && event.persisted) return true; 800 801 const nav = performance.getEntriesByType 802 ? performance.getEntriesByType('navigation')[0] 803 : null; 804 805 if (nav && nav.type === 'back_forward') return true; 806 807 if (performance.navigation && performance.navigation.type === 2) return true; 808 809 return false; 810 } 811 812 function restoreScroll(y) { 813 y = parseInt(y || 0, 10); 814 815 function run() { 816 window.scrollTo(0, y); 817 } 818 819 run(); 820 setTimeout(run, 50); 821 setTimeout(run, 150); 822 setTimeout(run, 350); 823 setTimeout(run, 700); 824 } 825 826 function restorePageState(state) { 827 const box = document.getElementById('ionoSearchBox'); 828 const result = document.getElementById('ionoSearchResult'); 829 830 if (!box || !result || !state) return false; 831 832 restoreFormState(box, state.formState); 833 updateRankingButton(box); 834 835 result.innerHTML = state.resultHtml || ''; 836 resetHoverImages(result); 837 838 initialParams = clone(state.initialParams || initialParams); 839 activeParams = clone(state.activeParams || initialParams); 840 dirtyFields = clone(state.dirtyFields || {}); 841 currentPage = parseInt(state.currentPage || 1, 10) || 1; 842 843 bindHover(result); 844 restoreScroll(state.scrollY); 845 846 debug('æ»ãç¶æ ã復å ãã¾ãã', { 847 currentPage: currentPage, 848 activeParams: activeParams, 849 dirtyFields: dirtyFields, 850 scrollY: state.scrollY, 851 htmlLength: result.innerHTML.length 852 }); 853 854 return true; 855 } 856 857 async function fetchItems(api, params) { 858 const query = new URLSearchParams(); 859 const normalized = normalizeParams(params || {}); 860 const publicParams = cleanParamsForUrl(normalized); 861
862 Object.keys(publicParams).forEach(function (key) { 863 const val = publicParams[key]; 864 if (val === undefined || val === null || val === '') return; 865 query.set(key, val); 866 }); 867 868 /** 869 * å ¬éURLã«ã¯åºããªãããã·ã§ã¼ãã³ã¼ãçã§ä¸ããããé ãæ¡ä»¶ã¯Ajaxã§ãç¶æããã 870 */ 871 ['codes', 'exclude_codes', 'materials', 'order', 'columns', 'include_desc'].forEach(function (key) { 872 const val = normalized[key]; 873 if (val === undefined || val === null || val === '') return; 874 query.set(key, val); 875 }); 876 877 const page = normalized.paged || normalized.npage || 1; 878 query.set('npage', page); 879 880 if (!query.has('format')) { 881 query.set('format', 'html'); 882 } 883 884 const url = api + '?' + query.toString(); 885 886 debug('APIãªã¯ã¨ã¹ãéå§', { 887 url: url, 888 params: normalized 889 }); 890 891 const requestUrl = new URL(url, location.href); 892 const isSameOrigin = requestUrl.origin === location.origin; 893 894 const res = await fetch(url, { 895 method: 'GET', 896 mode: 'cors', 897 // ä¼å¡éå®ã»ã¼ã«å¤å®ã«Welcartã®ãã°ã¤ã³Cookieãå¿ è¦ã 898 // åä¸ãã¡ã¤ã³ã®APIã ãCookieãéããå¤é¨APIã§ã¯å¾æ¥ã©ããéããªãã 899 credentials: isSameOrigin ? 'same-origin' : 'omit' 900 }); 901 902 if (!res.ok) { 903 throw new Error('API error: ' + res.status); 904 } 905 906 const text = await res.text(); 907 908 let data; 909 910 try { 911 data = JSON.parse(text); 912 } catch (e) { 913 debug('JSONã¨ãã¦èªãã¾ãã', { 914 responseText: text.slice(0, 2000) 915 }); 916 throw e; 917 } 918 919 return data; 920 } 921 922 function syncFormWithParams(box, params) { 923 box.querySelectorAll('input, select, textarea').forEach(function (el) { 924 if (!el.name) return; 925 926 const internalName = publicNameToInternal(el.name); 927 let value = params[internalName]; 928 929 if (value === undefined) return; 930 if (internalName === 'orderby') value = publicOrderby(value); 931 932 if (el.type === 'checkbox') { 933 const values = String(value).split(','); 934 el.checked = values.includes(el.value); 935 return; 936 } 937 938 if (el.type === 'radio') { 939 el.checked = String(value) === String(el.value); 940 return; 941 } 942 943 el.value = value; 944 }); 945 } 946 947 function updateRankingButton(box) { 948 if (!box) return; 949 950 const button = box.querySelector('.iono-ranking-button'); 951 if (!button) return; 952 953 const select = box.querySelector('select[name="orderby"]'); 954 const value = select ? select.value : (activeParams.orderby || ''); 955 const isSales = normalizeOrderby(value) === 'sales_amount'; 956 957 button.classList.toggle('is-hidden', isSales); 958 } 959 960 961 function initIonoItems(pageShowEvent) { 962 const box = document.getElementById('ionoSearchBox'); 963 const result = document.getElementById('ionoSearchResult'); 964 const status = document.getElementById('ionoSearchStatus'); 965 966 function setStatusText(text) { 967 if (!status) return; 968 status.textContent = text || ''; 969 if (text) { 970 status.hidden = false; 971 } else { 972 status.hidden = true; 973 } 974 } 975 const moreBtn = document.getElementById('ionoMoreBtn'); 976 const pagers = getPagerElements(); 977 978 if (!box || !result) { 979 debug('å¿ è¦ãªè¦ç´ ãããã¾ãã', { 980 hasSearchBox: !!box, 981 hasResult: !!result 982 }); 983 return; 984 } 985 986 if (box.dataset.ionoInitialized === '1') { 987 return; 988 } 989 990 box.dataset.ionoInitialized = '1'; 991 992 initializeIonoHistoryEntry(); 993 994 const api = box.dataset.api || DEFAULT_API; 995 const navMode = box.dataset.navMode || 'more'; 996 const skipInitialAjax = box.dataset.skipInitialAjax === '1'; 997 998 const submitBtn = box.querySelector('.iono-search-submit'); 999 const resetBtn = box.querySelector('.iono-search-reset'); 1000 const rankingBtn = box.querySelector('.iono-ranking-button'); 1001 1002 initialParams = parseInitialParams(box); 1003 activeParams = clone(initialParams); 1004 dirtyFields = {}; 1005 1006 syncFormWithParams(box, activeParams); 1007 updateRankingButton(box); 1008 1009 if (navMode === 'pager') { 1010 if (moreBtn) moreBtn.style.display = 'none'; 1011 } else { 1012 clearPagers(); 1013 } 1014 1015 async function runSearch(options = {}) { 1016 if (isLoading) return; 1017 1018 isLoading = true; 1019 1020 const append = !!options.append;
1021 const params = buildRequestParams(); 1022 1023 if (status) { 1024 setStatusText(append ? '追å èªã¿è¾¼ã¿ä¸...' : 'æ¤ç´¢ä¸...'); 1025 } 1026 1027 result.classList.add('is-loading'); 1028 1029 debug(append ? 'ãã£ã¨è¦ãéå§' : 'æ¤ç´¢éå§', { 1030 navMode: navMode, 1031 append: append, 1032 currentPage: currentPage, 1033 activeParams: activeParams, 1034 dirtyFields: dirtyFields, 1035 requestParams: params 1036 }); 1037 1038 try { 1039 const data = await fetchItems(api, params); 1040 1041 if (!append) { 1042 if (data.format === 'html') { 1043 result.innerHTML = data.html || ''; 1044 } else { 1045 result.innerHTML = renderItems(data.items || [], params); 1046 } 1047 } else { 1048 const temp = document.createElement('div'); 1049 1050 if (data.format === 'html') { 1051 temp.innerHTML = data.html || ''; 1052 } else { 1053 temp.innerHTML = renderItems(data.items || [], params); 1054 } 1055 1056 const newCards = temp.querySelectorAll('.iono-item-card'); 1057 1058 let grid = result.querySelector('.iono-items-grid'); 1059 1060 if (!grid) { 1061 const columns = Math.max(1, parseInt(activeParams.columns || 4, 10) || 4); 1062 grid = document.createElement('div'); 1063 grid.className = 'iono-items-grid iono-cols-' + columns + ' iono-items-after-ranking'; 1064 result.appendChild(grid); 1065 } 1066 1067 newCards.forEach(function (card) { 1068 grid.appendChild(card); 1069 }); 1070 1071 if (newCards.length === 0 && moreBtn) { 1072 moreBtn.style.display = 'none'; 1073 } 1074 } 1075 1076 bindHover(result); 1077 1078 if (navMode === 'pager') { 1079 renderPager(activeParams, currentPage, data, navMode); 1080 if (moreBtn) moreBtn.style.display = 'none'; 1081 } else { 1082 clearPagers(); 1083 if (moreBtn) moreBtn.style.display = ''; 1084 } 1085 1086 if (!append && options.updateUrl) { 1087 updateUrl(activeParams, currentPage, navMode); 1088 } 1089 1090 if (status) { 1091 setStatusText(''); 1092 } 1093 1094 savePageState(append ? 'more_loaded' : 'search_loaded'); 1095 1096 debug('è¡¨ç¤ºæ´æ°å®äº', { 1097 currentPage: currentPage, 1098 navMode: navMode, 1099 foundPosts: data.found_posts, 1100 maxPages: data.max_pages, 1101 cardCount: result.querySelectorAll('.iono-item-card').length, 1102 htmlLength: result.innerHTML.length 1103 }); 1104 1105 } catch (e) { 1106 console.error(e); 1107 1108 if (status) { 1109 setStatusText('ååãåå¾ã§ãã¾ããã§ããã'); 1110 } 1111 1112 debug('æ¤ç´¢ã¨ã©ã¼', { 1113 message: e.message, 1114 stack: e.stack 1115 }); 1116 1117 } finally { 1118 result.classList.remove('is-loading'); 1119 isLoading = false; 1120 } 1121 } 1122 1123 function searchFromFirstPageByField(name) { 1124 markFieldDirtyAndUpdate(box, name); 1125 updateRankingButton(box); 1126 1127 currentPage = 1; 1128 1129 if (moreBtn && navMode === 'more') { 1130 moreBtn.style.display = ''; 1131 } 1132 1133 runSearch({ 1134 updateUrl: true 1135 }); 1136 } 1137 1138 result.addEventListener('click', function (e) { 1139 const card = e.target.closest('.iono-item-card, .iono-ranking-card'); 1140 if (!card) return; 1141 1142 savePageState('item_click'); 1143 }); 1144 1145 if (rankingBtn) { 1146 rankingBtn.addEventListener('click', function () { 1147 const orderbySelect = box.querySelector('select[name="orderby"]'); 1148 if (!orderbySelect) return; 1149 1150 orderbySelect.value = 'sales'; 1151 searchFromFirstPageByField(orderbySelect.name); 1152 }); 1153 } 1154 1155 if (submitBtn) { 1156 submitBtn.addEventListener('click', function () { 1157 Object.keys(dirtyFields).forEach(function (name) { 1158 markFieldDirtyAndUpdate(box, name); 1159 }); 1160 1161 currentPage = 1; 1162 1163 if (moreBtn && navMode === 'more') { 1164 moreBtn.style.display = ''; 1165 } 1166 1167 runSearch({ 1168 updateUrl: true 1169 }); 1170 }); 1171 } 1172 1173 box.querySelectorAll('select').forEach(function (el) { 1174 el.addEventListener('change', function () { 1175 searchFromFirstPageByField(el.name); 1176 }); 1177 }); 1178 1179 box.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(function (el) { 1180 el.addEventListener('change', function () { 1181 searchFromFirstPageByField(el.name); 1182 }); 1183 }); 1184 1185 box.querySelectorAll('input[type="search"], input[type="text"], input[type="number"], input
1185:not([type])').forEach(function (el) { 1186 el.addEventListener('input', function () { 1187 dirtyFields[el.name] = true; 1188 1189 clearTimeout(searchTimer); 1190 1191 searchTimer = setTimeout(function () { 1192 searchFromFirstPageByField(el.name); 1193 }, 350); 1194 }); 1195 }); 1196 1197 if (resetBtn) { 1198 resetBtn.addEventListener('click', function () { 1199 sessionStorage.removeItem(STATE_KEY); 1200 1201 const currentHistoryState = history.state && typeof history.state === 'object' 1202 ? history.state 1203 : {}; 1204 1205 // ãã®ãã¼ã¸å ã§è¿½å ãããã£ã«ã¿ã¼å±¥æ´ãªããéè¤ãã空URLãä½ããåºç¹ã¸æ»ãã 1206 if (currentHistoryState.ionoItemsFiltered && currentHistoryState.ionoItemsHasBase) { 1207 history.back(); 1208 return; 1209 } 1210 1211 dirtyFields = {}; 1212 activeParams = clone(initialParams); 1213 currentPage = 1; 1214 1215 syncFormWithParams(box, activeParams); 1216 updateRankingButton(box); 1217 1218 if (moreBtn && navMode === 'more') { 1219 moreBtn.style.display = ''; 1220 } 1221 1222 clearIonoUrlParams(); 1223 1224 runSearch({ 1225 updateUrl: false 1226 }); 1227 }); 1228 } 1229 1230 if (moreBtn) { 1231 moreBtn.addEventListener('click', function () { 1232 if (navMode !== 'more') return; 1233 1234 currentPage++; 1235 1236 runSearch({ 1237 append: true 1238 }); 1239 }); 1240 } 1241 1242 const savedState = loadPageState(); 1243 const isBack = isBackForward(pageShowEvent); 1244 1245 if (isBack && savedState && savedState.resultHtml) { 1246 restorePageState(savedState); 1247 } else { 1248 resetHoverImages(result); 1249 bindHover(result); 1250 1251 currentPage = parseInt(initialParams.paged || initialParams.page_no || 1, 10) || 1; 1252 delete activeParams.paged; 1253 delete activeParams.page_no; 1254 1255 if (navMode === 'pager' && !skipInitialAjax) { 1256 /** 1257 * ååPHP表示ã§ããã¼ã¸ã£ã¼ãJSçæãããå ´åã ãAjaxã1åå©ãã 1258 * data-skip-initial-ajax="1" ã®å ´åã¯PHPå´ã§åºãããã¼ã¸ã£ã¼ã使ãã 1259 */ 1260 runSearch({ 1261 updateUrl: false 1262 }); 1263 } else { 1264 savePageState('initial_php_display'); 1265 1266 debug('åæåOKãååPHP表示ãç¶æãã¾ãã', { 1267 navMode: navMode, 1268 skipInitialAjax: skipInitialAjax, 1269 initialCardCount: result.querySelectorAll('.iono-item-card').length, 1270 currentPage: currentPage, 1271 initialParams: initialParams, 1272 activeParams: activeParams, 1273 dirtyFields: dirtyFields 1274 }); 1275 } 1276 } 1277 } 1278 1279 1280 1281 /** 1282 * ========================================================= 1283 * Public Function API 1284 * æ¢åã®èªååæåã¯æ®ããã¾ã¾ãJSãã颿°ã§ã使ããããã«ããã 1285 * ========================================================= 1286 * 1287 * åçº: 1288 * IonoItems.render('#target', { cat: 498, orderby: 'code_asc' }); 1289 * 1290 * ãã¼ã¸ã£ã¼ä»ã: 1291 * const list = IonoItems.create({ 1292 * target: '#target', 1293 * pager: ['#pagerTop', '#pagerBottom'], 1294 * params: { cat: 498, orderby: 'code_asc', limit: 24 } 1295 * }); 1296 * list.load(); 1297 */ 1298 1299 function ionoApiToElement(target) { 1300 if (!target) return null; 1301 if (typeof target === 'string') return document.querySelector(target); 1302 if (target.nodeType === 1) return target; 1303 return null; 1304 } 1305 1306 function ionoApiToElements(targets) { 1307 if (!targets) return []; 1308 if (!Array.isArray(targets)) targets = [targets]; 1309 return targets.map(ionoApiToElement).filter(Boolean); 1310 } 1311 1312 function ionoApiNormalizeParams(params) { 1313 return Object.assign({ 1314 orderby: 'recommend', 1315 order: 'DESC', 1316 limit: 24, 1317 columns: 4, 1318 paged: 1, 1319 format: 'html', 1320 tax_mode: 'tax_in' 1321 }, normalizeParams(params || {})); 1322 } 1323 1324 function ionoApiResetHoverImages(root) { 1325 if (!root) return; 1326 1327 root.querySelectorAll('img[data-main]').forEach(function (img) { 1328 if (img.dataset.main) img.src = img.dataset.main; 1329 }); 1330 } 1331 1332 function ionoApiMakePagerHtml(params, data) { 1333 const totalPages = parseInt(data && data.max_pages ? data.max_pages : 0, 10); 1334 const currentPage = parseInt(params.paged || 1, 10) || 1; 1335 1336 if (!totalPages || totalPages <= 1) return ''; 1337 1338 const maxLinks = 9; 1339 let start = Math.max(1, currentPage - 4); 1340 let end = Math.min(totalPages, start + maxLinks - 1); 1341 1342 if (end - start + 1 < maxLinks) { 1343 start = Math.max(1, end - maxLinks + 1); 1344 } 1345 1346 let html = '<div class="iono-pager">'; 1347 1348 if (currentPage > 1) {
1349 html += '<button type="button" class="iono-page-link iono-page-prev" data-page="' + (currentPage - 1) + '">åã¸</button>'; 1350 } else { 1351 html += '<span class="iono-page-disabled iono-page-prev">åã¸</span>'; 1352 } 1353 1354 if (start > 1) { 1355 html += '<button type="button" class="iono-page-link" data-page="1">1</button>'; 1356 if (start > 2) html += '<span class="iono-page-dots">...</span>'; 1357 } 1358 1359 for (let i = start; i <= end; i++) { 1360 if (i === currentPage) { 1361 html += '<span class="iono-page-current">' + i + '</span>'; 1362 } else { 1363 html += '<button type="button" class="iono-page-link" data-page="' + i + '">' + i + '</button>'; 1364 } 1365 } 1366 1367 if (end < totalPages) { 1368 if (end < totalPages - 1) html += '<span class="iono-page-dots">...</span>'; 1369 html += '<button type="button" class="iono-page-link" data-page="' + totalPages + '">' + totalPages + '</button>'; 1370 } 1371 1372 if (currentPage < totalPages) { 1373 html += '<button type="button" class="iono-page-link iono-page-next" data-page="' + (currentPage + 1) + '">次ã¸</button>'; 1374 } else { 1375 html += '<span class="iono-page-disabled iono-page-next">次ã¸</span>'; 1376 } 1377 1378 html += '</div>'; 1379 return html; 1380 } 1381 1382 function ionoApiCreate(options) { 1383 options = options || {}; 1384 1385 const target = ionoApiToElement(options.target); 1386 const pagerTargets = ionoApiToElements(options.pager); 1387 const api = options.api || DEFAULT_API; 1388 1389 let params = ionoApiNormalizeParams(options.params || {}); 1390 let lastData = null; 1391 let isApiLoading = false; 1392 1393 function drawPager(data, instance) { 1394 if (!pagerTargets.length) return; 1395 1396 const html = ionoApiMakePagerHtml(params, data); 1397 1398 pagerTargets.forEach(function (pager) { 1399 pager.innerHTML = html; 1400 pager.onclick = function (e) { 1401 const btn = e.target.closest('[data-page]'); 1402 if (!btn) return; 1403 1404 const page = parseInt(btn.dataset.page || 1, 10) || 1; 1405 instance.setParams({ paged: page }, { scroll: true }); 1406 }; 1407 }); 1408 } 1409 1410 async function load(loadOptions) { 1411 loadOptions = loadOptions || {}; 1412 1413 if (!target) { 1414 console.warn('IonoItems: target not found', options.target); 1415 return null; 1416 } 1417 1418 if (isApiLoading) return lastData; 1419 1420 isApiLoading = true; 1421 target.classList.add('is-loading'); 1422 1423 if (typeof options.onLoading === 'function') { 1424 options.onLoading(clone(params)); 1425 } 1426 1427 try { 1428 const requestParams = ionoApiNormalizeParams(params); 1429 const data = await fetchItems(api, requestParams); 1430 1431 lastData = data; 1432 1433 if (data.format === 'html') { 1434 target.innerHTML = data.html || ''; 1435 } else { 1436 target.innerHTML = renderItems(data.items || [], requestParams); 1437 } 1438 1439 ionoApiResetHoverImages(target); 1440 bindHover(target); 1441 drawPager(data, instance); 1442 1443 if (loadOptions.scroll) { 1444 const scrollTarget = options.scrollTarget === false 1445 ? null 1446 : ionoApiToElement(options.scrollTarget) || target; 1447 1448 if (scrollTarget) { 1449 scrollTarget.scrollIntoView({ behavior: 'smooth', block: 'start' }); 1450 } 1451 } 1452 1453 if (typeof options.onLoaded === 'function') { 1454 options.onLoaded(data, clone(requestParams)); 1455 } 1456 1457 return data; 1458 } catch (e) { 1459 console.error(e); 1460 1461 target.innerHTML = '<div class="iono-items-empty">ååãåå¾ã§ãã¾ããã§ããã</div>'; 1462 1463 pagerTargets.forEach(function (pager) { 1464 pager.innerHTML = ''; 1465 pager.onclick = null; 1466 }); 1467 1468 if (typeof options.onError === 'function') { 1469 options.onError(e, clone(params)); 1470 } 1471 1472 return null; 1473 } finally { 1474 target.classList.remove('is-loading'); 1475 isApiLoading = false; 1476 } 1477 } 1478 1479 function setParams(newParams, loadOptions) { 1480 params = ionoApiNormalizeParams(Object.assign({}, params, newParams || {})); 1481 return load(loadOptions); 1482 } 1483 1484 function getParams() { 1485 return clone(params); 1486 } 1487 1488 function getData() { 1489 return lastData; 1490 } 1491 1492 function destroy() {
1493 pagerTargets.forEach(function (pager) { 1494 pager.onclick = null; 1495 pager.innerHTML = ''; 1496 }); 1497 if (target) target.innerHTML = ''; 1498 } 1499 1500 const instance = { 1501 load: load, 1502 setParams: setParams, 1503 getParams: getParams, 1504 getData: getData, 1505 destroy: destroy 1506 }; 1507 1508 return instance; 1509 } 1510 1511 async function ionoApiRender(target, params, options) { 1512 options = options || {}; 1513 1514 const instance = ionoApiCreate(Object.assign({}, options, { 1515 target: target, 1516 params: params || {}, 1517 pager: options.pager || null 1518 })); 1519 1520 return await instance.load(); 1521 } 1522 1523 function ionoApiBindForm(formTarget, instance, map) { 1524 const form = ionoApiToElement(formTarget); 1525 if (!form || !instance) return; 1526 1527 map = map || {}; 1528 1529 function collectParams() { 1530 const collected = {}; 1531 1532 form.querySelectorAll('input, select, textarea').forEach(function (el) { 1533 if (!el.name) return; 1534 1535 const key = publicNameToInternal(map[el.name] || el.name); 1536 1537 if (el.type === 'checkbox') { 1538 if (!collected[key]) collected[key] = []; 1539 if (el.checked) collected[key].push(el.value); 1540 return; 1541 } 1542 1543 if (el.type === 'radio') { 1544 if (el.checked) collected[key] = el.value; 1545 return; 1546 } 1547 1548 collected[key] = el.value; 1549 }); 1550 1551 Object.keys(collected).forEach(function (key) { 1552 if (Array.isArray(collected[key])) { 1553 collected[key] = collected[key].join(','); 1554 } 1555 }); 1556 1557 collected.paged = 1; 1558 return collected; 1559 } 1560 1561 form.addEventListener('submit', function (e) { 1562 e.preventDefault(); 1563 instance.setParams(collectParams(), { scroll: true }); 1564 }); 1565 1566 form.querySelectorAll('select, input[type="checkbox"], input[type="radio"]').forEach(function (el) { 1567 el.addEventListener('change', function () { 1568 instance.setParams(collectParams(), { scroll: true }); 1569 }); 1570 }); 1571 1572 form.querySelectorAll('input[type="search"], input[type="text"], input[type="number"], input:not([type])').forEach(function (el) { 1573 let timer = null; 1574 el.addEventListener('input', function () { 1575 clearTimeout(timer); 1576 timer = setTimeout(function () { 1577 instance.setParams(collectParams(), { scroll: true }); 1578 }, 350); 1579 }); 1580 }); 1581 } 1582 1583 1584 1585 /** 1586 * ç°¡æååä¸è¦§: 横ã¹ã¯ãã¼ã«æä½ 1587 * ã¡ã¤ã³æ¤ç´¢ã¨ã¯å®å ¨ã«ç¬ç«ãè¤æ°è¨ç½®OKã 1588 */ 1589 function initSimpleCarousels(root) { 1590 const scope = root || document; 1591 bindHover(scope); 1592 1593 scope.querySelectorAll('[data-iono-simple-carousel]').forEach(function (carousel) { 1594 if (carousel.dataset.ionoSimpleInitialized === '1') return; 1595 carousel.dataset.ionoSimpleInitialized = '1'; 1596 1597 const scroller = carousel.querySelector('.iono-simple-scroll'); 1598 const prev = carousel.querySelector('.iono-simple-nav-prev'); 1599 const next = carousel.querySelector('.iono-simple-nav-next'); 1600 1601 if (!scroller) return; 1602 1603 function getStep() { 1604 const card = scroller.querySelector('.iono-simple-card'); 1605 if (!card) return Math.max(180, Math.floor(scroller.clientWidth * 0.75)); 1606 const rect = card.getBoundingClientRect(); 1607 return Math.max(160, Math.floor(rect.width * 2.2)); 1608 } 1609 1610 function updateNav() { 1611 const canScroll = scroller.scrollWidth > scroller.clientWidth + 4; 1612 carousel.classList.toggle('is-scrollable', canScroll); 1613 1614 if (!canScroll) return; 1615 1616 const maxLeft = scroller.scrollWidth - scroller.clientWidth - 4; 1617 carousel.classList.toggle('is-at-start', scroller.scrollLeft <= 4); 1618 carousel.classList.toggle('is-at-end', scroller.scrollLeft >= maxLeft); 1619 } 1620 1621 if (prev) { 1622 prev.addEventListener('click', function () { 1623 scroller.scrollBy({ left: -getStep(), behavior: 'smooth' }); 1624 }); 1625 } 1626 1627 if (next) { 1628 next.addEventListener('click', function () { 1629 scroller.scrollBy({ left: getStep(), behavior: 'smooth' }); 1630 }); 1631 } 1632 1633 let isDown = false;
1634 let startX = 0; 1635 let startLeft = 0; 1636 let isDragging = false; 1637 let suppressClick = false; 1638 const DRAG_THRESHOLD = 12; 1639 1640 scroller.addEventListener('pointerdown', function (e) { 1641 if (e.pointerType === 'mouse' && e.button !== 0) return; 1642 isDown = true; 1643 isDragging = false; 1644 suppressClick = false; 1645 startX = e.clientX; 1646 startLeft = scroller.scrollLeft; 1647 }); 1648 1649 scroller.addEventListener('pointermove', function (e) { 1650 if (!isDown) return; 1651 1652 const dx = e.clientX - startX; 1653 1654 // ã¯ãªãã¯æã®å¾®å¦ãªãã¬ã§ã¯ãªã³ã¯é·ç§»ã殺ããªãã 1655 if (!isDragging && Math.abs(dx) < DRAG_THRESHOLD) return; 1656 1657 if (!isDragging) { 1658 isDragging = true; 1659 suppressClick = true; 1660 scroller.classList.add('is-dragging'); 1661 try { 1662 scroller.setPointerCapture && scroller.setPointerCapture(e.pointerId); 1663 } catch (_) {} 1664 } 1665 1666 e.preventDefault(); 1667 scroller.scrollLeft = startLeft - dx; 1668 }); 1669 1670 function endDrag(e) { 1671 if (!isDown) return; 1672 isDown = false; 1673 isDragging = false; 1674 scroller.classList.remove('is-dragging'); 1675 try { 1676 scroller.releasePointerCapture && scroller.releasePointerCapture(e.pointerId); 1677 } catch (_) {} 1678 } 1679 1680 scroller.addEventListener('pointerup', endDrag); 1681 scroller.addEventListener('pointercancel', endDrag); 1682 scroller.addEventListener('mouseleave', function (e) { 1683 if (isDown) endDrag(e); 1684 }); 1685 1686 scroller.addEventListener('click', function (e) { 1687 if (!suppressClick) return; 1688 e.preventDefault(); 1689 e.stopPropagation(); 1690 suppressClick = false; 1691 }, true); 1692 1693 scroller.addEventListener('scroll', updateNav, { passive: true }); 1694 window.addEventListener('resize', updateNav); 1695 1696 updateNav(); 1697 setTimeout(updateNav, 120); 1698 setTimeout(updateNav, 500); 1699 1700 scroller.querySelectorAll('img').forEach(function (img) { 1701 if (!img.complete) { 1702 img.addEventListener('load', updateNav, { once: true }); 1703 img.addEventListener('error', updateNav, { once: true }); 1704 } 1705 }); 1706 }); 1707 } 1708 1709 window.IonoItems = { 1710 create: ionoApiCreate, 1711 render: ionoApiRender, 1712 fetch: fetchItems, 1713 bindForm: ionoApiBindForm, 1714 bindHover: bindHover, 1715 resetHoverImages: ionoApiResetHoverImages, 1716 initSimpleCarousels: initSimpleCarousels 1717 }; 1718 1719 if (document.readyState === 'loading') { 1720 document.addEventListener('DOMContentLoaded', function () { 1721 initIonoItems(null); 1722 initSimpleCarousels(document); 1723 }); 1724 } else { 1725 initIonoItems(null); 1726 initSimpleCarousels(document); 1727 } 1728 1729 /** 1730 * pushStateã§è¿½å ããåä¸ãã¼ã¸å ã®å±¥æ´ãæ»ã£ãå ´åã 1731 * URLã ãã§ãªãPHPåæè¡¨ç¤ºã確å®ã«å ã¸æ»ãã 1732 * åå詳細ãã¼ã¸ããæ»ã£ãå ´å㯠renderedUrl ã¨ä¸è´ããããåèªè¾¼ããªãã 1733 */ 1734 window.addEventListener('popstate', function (event) { 1735 if (location.href === renderedUrl) return; 1736 1737 const state = event.state && typeof event.state === 'object' 1738 ? event.state 1739 : {}; 1740 1741 if (state.ionoItemsBase || state.ionoItemsFiltered) { 1742 if (state.ionoItemsBase) { 1743 try { 1744 sessionStorage.removeItem(STATE_KEY); 1745 } catch (e) { 1746 // sessionStorageã使ããªãã¦ãå±¥æ´å¾©å èªä½ã¯ç¶è¡ããã 1747 } 1748 } 1749 1750 location.reload(); 1751 } 1752 }); 1753 1754 window.addEventListener('pageshow', function (event) { 1755 const savedState = loadPageState(); 1756 1757 if (isBackForward(event) && savedState && savedState.resultHtml) { 1758 const box = document.getElementById('ionoSearchBox'); 1759 1760 if (box) { 1761 box.dataset.ionoInitialized = ''; 1762 } 1763 1764 initIonoItems(event); 1765 } 1766 1767 resetHoverImages(document.getElementById('ionoSearchResult')); 1768 initSimpleCarousels(document); 1769 }); 1770 1771})();
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.