1(function(modules) { 2 'use strict'; 3 4 const { root, document, $ } = modules; 5 6 const utils = (function() { 7 const http = (url, params = undefined) => { 8 return new Promise((resolve, reject) => { 9 $.ajax({ 10 url, 11 data: params, 12 cache: false, 13 async: true, 14 type: 'POST', 15 dataType: 'json', 16 success: function(res) { 17 const isSuccess = res.msg === 'SUCCESS'; 18 19 if (!isSuccess) { 20 return reject(res); 21 } 22 23 resolve(res); 24 }, 25 }); 26 }); 27 } 28 29 const bind = (instance, fn) => { 30 return function(...args) { 31 fn.call(instance, ...args); 32 }; 33 } 34 35 const isLogin = () => { 36 /** 37 * ë¡ê·¸ì¸ ì¬ë¶ ì²´í¬ 38 * @ref FO, global_vars.sub 39 */ 40 return !window.IS_GUEST; 41 } 42 43 const getMemberHash = () => { 44 /** 45 * í´ì±ë ë©¤ë² í¤ 46 * @ref FO, global_vars.sub 47 */ 48 return window.MEMBER_HASH; 49 } 50 51 return { http, bind, isLogin, getMemberHash }; 52 })(); 53 54 const localize = (function() { 55 const íì´í_ìµê·¼ë³¸ìí = () => { 56 return getLocalizeString('íì´í_ìµê·¼ë³¸ìí', undefined, 'ìµê·¼ 본 ìí'); 57 } 58 59 return { íì´í_ìµê·¼ë³¸ìí }; 60 })(); 61 62 const cookie = (function() { 63 const get = (key) => { 64 return `; ${document.cookie}` 65 .split(`; ${key}=`)[1] 66 ?.split(';')[0] ?? null; 67 } 68 69 const has = (key) => { 70 return get(key) !== null; 71 } 72 73 const set = (key, value = '', maxAge = undefined) => { 74 document.cookie = `${key}=${value}; path=/;${maxAge ? ` max-age=${maxAge}` : ''}`; 75 } 76 77 const remove = (key) => { 78 document.cookie = `${key}=''' path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`; 79 } 80 81 return { get, has, set, remove }; 82 })(); 83 84 const keyStore = (function() { 85 const PREFIX = '_rp'; 86 const SEPARATOR = '_'; 87 88 const generateCacheExpiresKey = () => { 89 return [PREFIX, 'c', utils.getMemberHash()].join(SEPARATOR); 90 } 91 92 const generateMemberKeyByMemberHash = () => { 93 return [PREFIX, 'm', utils.getMemberHash()].join(SEPARATOR); 94 } 95 96 return { 97 CACHE_KEY: generateCacheExpiresKey(), 98 MEMBER_KEY: generateMemberKeyByMemberHash(), 99 }; 100 })(); 101 102 const apis = (function() { 103 const CONSTANTS = { 104 HTTP_ERROR_CODE: { 105 NOT_ENABLED_SITE: 'not enabled site', 106 NEED_TO_LOGIN: 'need to login', 107 FAIL_TO_SEARCH: 'Fail to Search', 108 }, 109 }; 110 111 const getRecentProducts = async () => { 112 try { 113 const res = await utils.http('/ajax/shop/get_recent_view_prod_list.cm'); 114 115 return res['recent_view_prod_list']; 116 } catch (e) { 117 const errorCode = e.message; 118 119 /** ì¡°í ì¤í¨ì¸ ê²½ì°, ë¹ ë°°ì´ ì²ë¦¬ */ 120 if (errorCode === CONSTANTS.HTTP_ERROR_CODE.FAIL_TO_SEARCH) { 121 return []; 122 } 123 124 throw e; 125 } 126 } 127 128 return { getRecentProducts }; 129 })(); 130 131 const cache = (function() { 132 const MAX_READ_COUNT = 5; 133 const MAX_CACHE_COUNT = 100; 134 135 const storage = window.localStorage; 136 137 const encode = (source) => { 138 return btoa(encodeURIComponent(source)); 139 } 140 141 const decode = (source) => { 142 return decodeURIComponent(atob(source)); 143 } 144 145 const isExpires = () => { 146 return !cookie.has(keyStore.CACHE_KEY); 147 } 148 149 const isEmpty = () => { 150 return get().length === 0; 151 } 152 153 const get = () => { 154 try { 155 const currentRecentProducts = storage.getItem(keyStore.MEMBER_KEY); 156 157 if (currentRecentProducts === null) { 158 return []; 159 } 160 161 return JSON.parse(decode(currentRecentProducts)) ?? []; 162 } catch (e) { 163 return []; 164 } 165 } 166 167 const set = (recentProducts) => { 168 storage.setItem(keyStore.MEMBER_KEY, encode(JSON.stringify(recentProducts))); 169 } 170 171 const clear = () => { 172 storage.removeItem(keyStore.MEMBER_KEY); 173 174 cookie.remove(keyStore.CACHE_KEY); 175 } 176 177 const has = (recentProdeucts, targetRecentProduct) => { 178 return recentProdeucts.some(recentProduct => recentProduct.code === targetRecentProduct.code); 179 } 180 181 const read = () => { 182 if (isExpires()) { 183 return []; 184 } 185 186 return get().slice(0, MAX_READ_COUNT); 187 } 188 189 const write = (recentProducts) => { 190 set(recentProducts.slice(0, MAX_CACHE_COUNT)); 191 192 /** ë°ì´í°ê° ìì ë ê²½ì° cache ìê° ì¦ê°(1ìê°) */ 193 cookie.set(keyStore.CACHE_KEY, '1', 60 * 60); 194 } 195 196 const add = (recentProducts) => { 197 const currentRecentProducts = get(); 198 199 const targetRecentProducts = recentProducts.reduce((acc, cur) => { 200 if (has(currentRecentProducts, cur)) { 201 return acc; 202 } 203 204 acc.push(cur); 205 206 return acc; 207 }, []); 208 209 write([ 210 ...targetRecentProducts, 211 ...currentRecentProducts, 212 ]); 213 } 214 215 return { 216 isExpires, 217 isEmpty, 218 clear, 219 read, 220 write, 221 add, 222 }; 223 })(); 224 225 const renderer = (function () { 226 const CONSTANTS = { 227 ROOT: 'recent-product-root', 228 }; 229 230 const escapeHtml = (source) => { 231 const text = String(source ?? ''); 232 return text 233 .replace(/&/g, '&') 234 .replace(/</g, '<') 235 .replace(/>/g, '>') 236 .replace(/"/g, '"') 237 .replace(/'/g, '''); 238 } 239 240 const safeUrl = (source) => { 241 const url = String(source ?? '').trim(); 242 if (url === '' || /^\s*javascript:/i.test(url)) { 243 return '#'; 244 } 245 return url; 246 } 247 248 const refine = (strings) => { 249 return strings.map(string => string.replace(/[\r\n\t]+/g, '').trim()); 250 } 251 252 const combine = (chunks, args) => { 253 return chunks.reduce((acc, cur, index) => `${acc}${cur}${args[index] ?? ''}`, ''); 254 } 255 256 const html = (tags, ...args) => { 257 return combine(refine(tags), refine(args)); 258 } 259 260 const createContentHtml = (children) => { 261 return html` 262 <div class="recent-product"> 263 <div class="recent-product__inner"> 264 <p class="recent-product__title">${localize.íì´í_ìµê·¼ë³¸ìí()}</p> 265 ${children} 266 </div>
267 </div> 268 `; 269 } 270 271 const createProductItemContainerHtml = (children) => { 272 return html` 273 <div class="recent-product__item-container"> 274 ${children} 275 </div> 276 `; 277 } 278 279 const createProductItemHtml = (product) => { 280 const safeLinkUrl = escapeHtml(safeUrl(product.link_url)); 281 const safeCode = escapeHtml(product.code); 282 const safeThumbnail = escapeHtml(safeUrl(product.thumbnail)); 283 const safeName = escapeHtml(product.name); 284 285 return html` 286 <a class="recent-product__item-root" href="${safeLinkUrl}" data-code="${safeCode}"> 287 <div class="recent-product__item-img-wrapper"> 288 <img src="${safeThumbnail}" alt="${safeName}" class="recent-product__item-img" /> 289 </div> 290 <p class="recent-product__item-name">${safeName}</p> 291 </a> 292 `; 293 } 294 295 const createRootElement = () => { 296 const $root = document.createElement('div'); 297 298 $root.setAttribute('id', CONSTANTS.ROOT); 299 300 $root.style.setProperty('display', 'contents'); 301 302 return $root; 303 } 304 305 const render = (products) => { 306 const $root = createRootElement(); 307 const recentProductsHtml = createContentHtml( 308 createProductItemContainerHtml( 309 products.map(product => createProductItemHtml(product)).join(''), 310 ), 311 ); 312 313 $root.innerHTML = recentProductsHtml; 314 315 document.body.appendChild($root); 316 } 317 318 const destroy = () => { 319 const $root = document.getElementById(CONSTANTS.ROOT); 320 321 if ($root) { 322 $root.remove(); 323 } 324 } 325 326 return { render, destroy }; 327 })(); 328 329 function RecentProduct() { 330 this.recentProductQueue = []; 331 this.mount = false; 332 } 333 334 RecentProduct.prototype.isNonMember = function() { 335 return !utils.isLogin(); 336 } 337 338 RecentProduct.prototype.enqueueRecentProduct = function(recentProduct) { 339 this.recentProductQueue.push(recentProduct); 340 } 341 342 RecentProduct.prototype.clearRecentProductQueue = function() { 343 this.recentProductQueue = []; 344 } 345 346 RecentProduct.prototype.hasRecentProductQueue = function() { 347 return this.recentProductQueue.length !== 0; 348 } 349 350 RecentProduct.prototype.isEmpty = function(recentProducts) { 351 return recentProducts.length === 0; 352 } 353 354 RecentProduct.prototype.prepare = async function() { 355 /** ë§ë£ ëìì ê²½ì° clear */ 356 if (cache.isExpires()) { 357 cache.clear(); 358 359 const recentProducts = await apis.getRecentProducts(); 360 361 cache.write(recentProducts); 362 } 363 364 /** ìí ìì¸ìì ë°ì´í°ë¥¼ ì¶ê°í ê²½ì°, ìºìì ì ì¥ */ 365 if (this.hasRecentProductQueue()) { 366 cache.add(this.recentProductQueue); 367 368 this.clearRecentProductQueue(); 369 } 370 } 371 372 RecentProduct.prototype.getRecentProducts = async function() { 373 return cache.read(); 374 } 375 376 RecentProduct.prototype.show = async function() { 377 if (this.mount) { 378 return; 379 } 380 381 this.mount = true; 382 383 try { 384 await this.prepare(); 385 386 const recentProducts = await this.getRecentProducts(); 387 388 if (this.isEmpty(recentProducts)) { 389 return; 390 } 391 392 renderer.destroy(); 393 renderer.render(recentProducts); 394 } catch (e) { 395 console.error(e.message); 396 } 397 } 398 399 RecentProduct.prototype.refresh = async function() { 400 if (!this.mount) { 401 return; 402 } 403 404 try { 405 renderer.destroy(); 406 407 await this.prepare(); 408 409 const recentProducts = await this.getRecentProducts(); 410 411 if (this.isEmpty(recentProducts)) { 412 return; 413 } 414 415 renderer.render(recentProducts); 416 } catch (e) { 417 console.error(e.message); 418 } 419 } 420 421 const recentProduct = new RecentProduct(); 422 423 const plugin = function() {} 424 425 plugin.show = utils.bind(recentProduct, function() { 426 if (this.isNonMember()) { 427 return; 428 } 429 430 this.show(); 431 }); 432 433 plugin.refresh = utils.bind(recentProduct, function() { 434 if (this.isNonMember()) { 435 return; 436 } 437 438 this.refresh(); 439 }); 440 441 plugin.enqueueRecentProduct = utils.bind(recentProduct, function(recentProduct) { 442 if (this.isNonMember()) { 443 return; 444 } 445 446 this.enqueueRecentProduct(recentProduct); 447 }); 448 449 root.RECENT_PRODUCT = plugin; 450})(function(root) { 451 const modules = { 452 root, 453 document: root.document, 454 $ : root.$, 455 }; 456 457 return modules; 458}(window));
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.