PageSourceSearch

https://greenjuice.co.kr/resources/js/fdd.js?v=20260105

js greenjuice.co.kr collected 2026-10-06 03:32:27 UTC 53,712 bytes, 1,568 lines download raw bytes

1function snsLoginCallback() {
2  var url = $("input[name=redirectUrl]").val();
3  if (url != undefined && url != "") {
4    location.href = url;
5    return;
6  }
7  location.href = "/";
8}
9
10// TODO: S2S 안착 확인되고 구방식(CheckPlus) 서비스 완전 종료되면 이 함수 삭제 대상
11// (niceWindowOpenS2S()는 계속 사용 — 삭제 대상 아님)
12function niceWindowOpen(name, data) {
13  window.open(
14      "https://nice.checkplus.co.kr/CheckPlusSafeModel/checkplus.cb?m=checkplusSerivce&EncodeData="
15      + data, "nicePopup",
16      'width=500, height=550, top=100, left=100, fullscreen=no, menubar=no, status=no, toolbar=no, titlebar=yes, scrollbar=no');
17}
18
19// nice S2S 전환용 신규 함수. 기존 niceWindowOpen()과 달리 URL을 조립하지 않고
20// 서버가 이미 완성해서 준 auth_url을 그대로 여는 역할만 함.
21// 이 함수는 계속 남아서 쓰인다 (삭제 대상 아님).
22function niceWindowOpenS2S(authUrl) {
23  window.open(authUrl, "nicePopup",
24      'width=500, height=550, top=100, left=100, fullscreen=no, menubar=no, status=no, toolbar=no, titlebar=yes, scrollbar=no');
25}
26
27function more(url, param, callback) {
28  if (param === undefined || param === null) {
29    param = {};
30  }
31  if (param.pageNo === undefined) {
32    param.pageNo = 1;
33  } else {
34    param.pageNo = (parseInt(param.pageNo) + 1);
35  }
36
37  get({
38    "url": url,
39    "param": param
40  }, function (response) {
41    response = response.RESULT_MSG;
42    callback(response);
43  });
44}
45
46function showNotAvailModal(fails, onOk, onCancel) {
47  var tpl = $("#noAvailTpl").text();
48  $("#noAvailModal .product-content-list").html("");
49  if (onCancel) {
50    $("#noAvailModal [data-cancelable]").show();
51    $("#noAvailModal [data-btn='c']").click(onCancel);
52    $("#noAvailModal [data-all-ignore]").hide().prev().show();
53  } else {
54    $("#noAvailModal [data-cancelable]").hide();
55    $("#noAvailModal [data-btn='c']").click(undefined);
56    $("#noAvailModal [data-all-ignore]").show().prev().hide();
57  }
58
59  $("#noAvailModal [data-btn='o']").click(function () {
60    $("#noAvailModal").modal("hide");
61    if (onOk) onOk();
62  });
63
64  for (var fail of fails) {
65    var t = tpl;
66    for (var k of Object.keys(fail)) {
67      var regex = new RegExp(`\\{${k}\\}`, 'g');
68      if (k === "price") {
69        t = t.replace(regex, String(fail[k]).replace(/\B(?=(\d{3})+(?!\d))/g, ','));
70      }
71      else {
72        t = t.replace(regex, fail[k]);
73      }
74    }
75    $("#noAvailModal .product-content-list").append(t);
76  }
77  $("#noAvailModal").modal("show");
78}
79
80function addCartToServer(type, data, eventIdx, pushData) {
81  var codes = [];
82  for (var item of data) {
83    if (!item.itemCode) continue;
84    codes.push(item.itemCode);
85  }
86
87  if(pushData){
88    try {
89      var push = new Object();
90      push.event = "add_to_cart";
91      push.ecommerce = [];
92
93      var ecommerce = {};
94      ecommerce.section = pushData.section;
95      ecommerce.type = type === "daily" ? "매일배송" : "택배배송";
96      if(pushData.recommend_type){
97        ecommerce.recommend_type = pushData.recommend_type;
98      }
99      ecommerce.items = [];
100      var items = [];
101
102      $.each(data,function (idx,item){
103        if(item.itemCode) {
104          $.ajax({
105            type: 'POST',
106            async: false,
107            url: '/findCartMenuInfo',
108            data: {itemCode: item.itemCode},
109            error: function (request, status, error) {
110              console.log("code:" + request.status + "\n" + "message:" + request.responseText + "\n" + "error:" + error);
111            },
112            success: function (result) {
113              if (result.RESULT_MSG.data.length > 0) {
114                if (result.RESULT_MSG.data[0].ITEMCODE == item.itemCode) {
115                  var response = result.RESULT_MSG.data[0];
116                  var itemObj = new Object();
117                  itemObj.item_id = String(response.IDX);
118                  itemObj.item_name = response.PNAME;
119                  itemObj.item_category = (type === "daily") ? "매일배송" : "택배배송";
120                  itemObj.item_category2 = response.P1_NAME;
121                  if (type == "daily") {
122                    itemObj.item_variant = Array.isArray(item.dayQty) && item.dayQty.length >= 5
123                        ? item.dayQty.slice(0, 5).join(',')
124                        : '0,0,0,0,0';
125
126                    itemObj.quantity = Array.isArray(item.dayQty)
127                        ? (item.dayQty.reduce((sum, current) => sum + Number(current), 0)) * 4
128                        : 0;
129                  } else {
130                    itemObj.quantity = item.qty;
131                  }
132
133                  if (pushData.affiliation) {
134                    itemObj.affiliation = pushData.affiliation;
135                  }
136
137                  itemObj.price = Number(response.PRICE);
138                  if (response.SALE_PRICE) {
139                    itemObj.discount = Number(response.PRICE) - Number(response.SALE_PRICE);
140                  } else {
141                    itemObj.discount = 0;
142                  }
143                  items.push(itemObj);
144                }
145              }
146            }
147          });
148        }
149      });
150
151      ecommerce.items = items;
152      push.ecommerce = ecommerce;
153
154    } catch (e) {
155      console.log(e);
156    }
157  }
158
159  axios.post(`/product_available`, { ids: codes }).then(function (r) {
160    var o = r.data.RESULT_MSG;
161    var lockIds = o.fails.map(x => x.itemCode);
162    var target = data.filter(x => lockIds.indexOf(x.itemCode) < 0);
163
164    let unavailableProdIdxes = o.fails.map(x => x.idx);
165    if(unavailableProdIdxes.length > 0) {
166      push.ecommerce.items = push.ecommerce.items.filter(item => !unavailableProdIdxes.includes(item.item_id));
167    }
168
169    axios.post('/cart/save', {[type]: target, eventIdx}).then(function ({data}) {
170      if (o.fails.length) {
171        var nextDisabled = o.fails.length >= codes.length;
172
173        if (!nextDisabled && push.ecommerce.items.length > 0) {
174          safeDataLayerPush(push);
175        }
176        showNotAvailModal(o.fails, nextDisabled ? undefined : function () {
177          location.href = '/cart/' + type;
178        }, nextDisabled ? undefined : function () {
179          $("#noAvailModal").modal("hide");
180        });
181      } else {
182        if (data.RESULT_ST == 'FAIL') {
183          alert(data.RESULT_MSG);
184        } else {
185          if ( push.ecommerce.items.length > 0) {
186            safeDataLayerPush(push);
187          }
188
189          $("[data-cart-size]").text(data.RESULT_MSG.cartSize);
190          if (data.RESULT_MSG.cartSize > 0) {
191            $("[data-cart-size]").removeClass("hide");
192          } else {
193            $("[data-cart-size]").addClass("hide");
194          }
195          confirmDesign("", data.RESULT_MSG.message, function () {
196            location.href = '/cart/' + type;
197          });
198        }
199      }
200    }).catch(function (e) {
201      alert("서버와 연결이 올바르지 않습니다.");
202    })
203
204  });
205
206}
207
208function addCart(type, itemCode, options, pushData) {
209  var orderItems = [];
210  if (type === 'daily') {
211    var item = {itemCode, dayQty: options.dayQty || [1, 1, 1, 1, 1]};
212    orderItems.push(item);
213  } else {
214    var item = {itemCode, qty: options.qty || 1};
215    orderItems.push(item);
216  }
217  addCartToServer(type, orderItems, options.eventIdx, pushData);
218}
219
220
221/*
222items = [{ code: '111', qty: 3 }] or [{ code: '111', qty: [1,1,1,0,0] }]
223 */
224function addCarts(type, items, eventIdx, pushData) {
225  var orderItems = [];
226  for (var item of items) {
227    if (type === 'daily') {
228      var dayQty = [1, 1, 1, 1, 1];
229      if (item.qty) {
230        if ($.isArray(item.qty)) {
231          dayQty = item.qty;
232        } else {
233          dayQty = [item.qty, item.qty, item.qty, item.qty, item.qty];
234        }
235      }
236      orderItems.push({
237        itemCode: item.itemCode,
238        dayQty
239      });
240    } else {
241      orderItems.push({
242        itemCode: item.itemCode,
243        qty: item.qty
244      });
245    }
246  }
247
248  addCartToServer(type, orderItems, eventIdx, pushData);
249}
250
251
252function addLike(type, itemCode, options) {
253
254}
255
256(function ($) {
257
258  $.fn.callText = function () {
259
260    $(this).each(function (ix, el) {
261      var elem = $(el);
262      if (elem.is("input")) {
263        if (elem.data("listener")) return;
264
265        console.log("!!!! ", el);
266
267        var clone = elem.clone();
268        clone.attr("name", "_x_" + clone.attr("name"));
269        clone.removeAttr("data-call-text");
270        clone.removeAttr("id");
271
272        elem.parent().append(clone);
273        $(el).attr("type", "hidden").data("listener", clone);
274
275        clone.on("input", function (e) {
276          var text = e.target.value;
277          text = text.replace(/[^0-9]/g, '');
278          if (text.startsWith("02")) {
279            text = text.replace(/^(\d{2})(\d{3,4})(\d{4})$/, `$1-$2-$3`);
280          } else {
281            text = text.replace(/^(\d{2,3})(\d{3,4})(\d{4})$/, `$1-$2-$3`);
282          }
283          elem.val(text.replace(/[^0-9]/g, ''));
284          if (e.target.value != text) {
285            clone.val(text);
286          }
287        });
288      } else {
289        var text = $(el).text().replace(/[^0-9]/g, '');
290        if (text.startsWith("02")) {
291          text = text.replace(/^(\d{2})(\d{3,4})(\d{4})$/, `$1-$2-$3`);
292        } else {
293          text = text.replace(/^(\d{2,3})(\d{3,4})(\d{4})$/, `$1-$2-$3`);
294        }
295        $(el).text(text);
296      }
297    })
298  }
299
300  $.fn.creditText = function () {
301
302    $(this).each(function (ix, el) {
303      var elem = $(el);
304      if (elem.is("input")) {
305        if (elem.data("listener")) return;
306
307        console.log("!!!! ", el);
308
309        var clone = elem.clone();
310        clone.attr("name", "_x_" + clone.attr("name"));
311        clone.removeAttr("data-credit-text");
312
313        elem.parent().append(clone);
314        $(el).attr("type", "hidden").data("listener", clone);
315
316        clone.on("input", function (e) {
317          var text = e.target.value;
318          elem.val(text.replace(/[^0-9]/g, ''));
319          text = text.replace(/[^0-9]/g, '').replace(/^(\d{4})(\d{4})(\d{4})(\d{3,4})$/, `$1-$2-$3-$4`);
320
321
322          if (e.target.value != text) {
323            clone.val(text);
324          }
325        });
326      } else {
327        $(el).text($(el).text().replace(/[^0-9]/g, '').replace(/^(\d{4})(\d{4})(\d{4})(\d{3,4})$/, `$1-$2-$3-$4`));
328      }
329    })
330  }
331
332  //카드번호 입력시 마스킹(*) 처리
333  let [cardfrontRrnNoInputStart, cardfrontRrnNoInputEnd] = [0, 0];// 입력 위치 확인하기위한 변수
334  let viewCardEl = '';
335  let hideCardEl = '';
336  let realCardEl  = '';
337
338  /*마스킹 설정*/
339  let cardMaskRange      = [7, 13];    // 마스킹 처리 범위[시작, 끝] ex) 4이후부터 8까지
340  let cardMaskText          = '*';        // 마스킹 처리할 문자
341  let cardHipenPosition  = [5, 10, 15];// 하이픈 위치
342  let cardInputMinLength  = 15;         // 입력제한
343  let cardInputMaxLength  = 16;         // 입력제한
344// 선택 시 해당 위치 저장
345  function valueSelect(e){
346    cardfrontRrnNoInputStart = e.target.selectionStart;
347    cardfrontRrnNoInputEnd   = e.target.selectionEnd;
348  }
349// 중간 잘라내기
350  function valueSlice(str, start, end, midStr, inputType){
351    const front = str.slice(0, start);
352    const back  = str.slice(end);
353    if (midStr) return front + midStr + back;
354    else return front + back;
355  }
356
357// 입력 시 마스킹 처리하기
358  function maskCardNumber(e){
359    // 입력제한 있는 경우
360    if(typeof cardInputMaxLength !== 'undefined'){
361      // 하이픈 사용하고 하이픈 들어가는 개수가 있는 경우
362      e.target.setAttribute('maxLength', parseInt(cardInputMaxLength) + parseInt(cardHipenPosition.length));
363    }
364
365    const data = e.originalEvent.data;     // 입력 데이터
366    const inputType = e.originalEvent.inputType;
366// 입력 방식
367    let position = e.target.selectionStart;// 입력 후 시작위치
368
369    // console.log('>>> e : ', e);
370    // console.log('입력데이터 : ', data);
371    // console.log('입력 방식 : ', inputType);
372    // console.log('입력 후 시작위치 : ', position);
373    // console.log('cardfrontRrnNoInputStart : ', cardfrontRrnNoInputStart);
374    // console.log('cardfrontRrnNoInputEnd : ', cardfrontRrnNoInputEnd);
375    // console.log('기존 hideCardEl.value : ', hideCardEl.value);
376
377    // 삭제
378    // 백스페이스의 경우
379    if(inputType === 'deleteContentBackward'){
380      // 기본
381      if (cardfrontRrnNoInputStart === cardfrontRrnNoInputEnd) {
382        // 입력 후 위치가 하이픈 넣을 위치면 하이픈 이전으로 옮겨주기 위헤
383        // 위치 값 -1
384        let temp = position + 1;
385        if(position === cardHipenPosition[0]-1 || position === cardHipenPosition[1]-1 || position === cardHipenPosition[2]-1){
386          position--;
387          temp--;
388        }
389        hideCardEl.value = valueSlice(hideCardEl.value, position, temp);
390      }
391      // 드래그 삭제
392      else {
393        hideCardEl.value = valueSlice(hideCardEl.value, cardfrontRrnNoInputStart, cardfrontRrnNoInputEnd);
394      }
395    }
396    // delete키의 경우
397    if(inputType === 'deleteContentForward'){
398      // 기본
399      if (cardfrontRrnNoInputStart === cardfrontRrnNoInputEnd) {
400        // 입력 후 위치가 하이픈 넣을 위치면 하이픈 다음으로 옮겨주기 위헤
401        // 위치 값 + 1
402        let temp = position + 1;
403        if(position+1 === cardHipenPosition[0] || position+1 === cardHipenPosition[1] || position+1 === cardHipenPosition[2]){
404          temp++;
405        }
406        hideCardEl.value = valueSlice(hideCardEl.value, position, temp, false, inputType);
407      }
408      // 드래그 삭제
409      else {
410        hideCardEl.value = valueSlice(hideCardEl.value, cardfrontRrnNoInputStart, cardfrontRrnNoInputEnd);
411      }
412    }
413
414    // 입력
415    else if (parseInt(data) >= 0 && parseInt(data) <= 9){
416      if (cardfrontRrnNoInputStart === cardfrontRrnNoInputEnd) {
417        hideCardEl.value = valueSlice(hideCardEl.value, position - 1, position - 1, data);
418      }
419      // 드래그 후 입력
420      else {
421        hideCardEl.value = valueSlice(hideCardEl.value, cardfrontRrnNoInputStart, cardfrontRrnNoInputEnd, data);
422      }
423      // 위치저장
424      cardfrontRrnNoInputStart = position;
425      cardfrontRrnNoInputEnd   = position;
426    }
427
428    // console.log('valueSlice리턴 >> hideCardEl.value : ', hideCardEl.value);
429
430    // console.log(maskRange);
431    let tempMaskRange = cardMaskRange;
432
433    // 하이픈 사용 시 조정 start ---
434    // 마스크 처리 부분은
435    if(hideCardEl.value !== ''){
436      // 하이픈 넣어주면서 생기는 커서 위치 pos 조정
437      hideCardEl.value = hideCardEl.value.replaceAll('-', '').match(/[0-9*]{1,4}/g)?.join('-');
438    }
439    // 하이픈 위치 저장 변수 체크하기 위해 반복
440    for(let i = 0; i < cardHipenPosition.length; i++){
441      // 입력 후 위치가 하이픈 넣을 위치면 하이픈 다음으로 옮겨주기 위헤
442      // 위치 값 + 1
443      if(position === cardHipenPosition[i]){
444        position = position + 1;
445      }
446
447      // 마스크 범위 수정
448      if((cardHipenPosition[i] - 1) === cardMaskRange[0]){
449        tempMaskRange[0] = cardMaskRange[0] + i + 1;
450      }
451      if((cardHipenPosition[i] - 1) === cardMaskRange[1]){
452        tempMaskRange[1] = cardMaskRange[1] + i + 1;
453      }
454    }
455    //하이픈 사용 시 조정 end ---
456
457    // console.log('입력 후 hideCardEl.value : ', hideCardEl.value);
458
459    const value = hideCardEl.value;
460    const splitValue = value.split('');
461    const maskValue = splitValue.map((v, i) => {
462      if (i >= parseInt(tempMaskRange[0]) && i <= parseInt(tempMaskRange[1]) && v !== '-') return cardMaskText;
463      else return v;
464    }).join('');
465    // 치환 값 넣기
466    e.target.value = maskValue;
467
468    // 바뀐 위치 원상복구
469    e.target.selectionStart = position;
470    e.target.selectionEnd   = position;
471
472    //실제로 db에 넘길 카드번호 담아주기
473    realCardEl.value = hideCardEl.value.replace(/[^0-9]/g, '');
474    // console.log('realCardEl.value : ', realCardEl.value);
475  }
476
477  $.fn.maskCreditText = function () {
478    $(this).each(function (ix, el) {
479      var elem = $(el);
480      if (elem.is("input")) {
481
482        elem.attr({
483          'minlength' : cardInputMinLength,
484          'maxlength' : cardInputMaxLength
485        });
486
487        //사용자가 입력하고 마스킹 처리되는 입력란 생성 > clone
488        var clone = elem.clone();
489        clone.attr({
490          "id" : "view-card-number",
491          "name" : "view-card-number"
492        });
493        clone.removeAttr("data-credit-text");
494        elem.parent().append(clone);
495
496        //사용자가 입력한 번호 그대로 가지고 있는 입력란 생성 > clone2
497        let clone2 = elem.clone();
498        clone2.attr({
499          "id" : "hide-card-number",
500          "name" : "hide-card-number",
501          "type" : "hidden"
502        });
503        clone2.removeAttr("data-credit-text");
504        elem.parent().append(clone2);
505
506        $(el).attr("type", "hidden");
507        $(el).addClass("real-card-number");
508
509        //실제로 값을 넘길 태그, 보여지는 태그 지정
510        viewCardEl =  clone[0];
511        hideCardEl = clone2[0];
512        realCardEl = elem[0];
513
514        // console.log(">>> maskCreditText() > hideCardEl(실제값 담는 태그) : ", hideCardEl);
515        // console.log(">>> maskCreditText() > viewCardEl(보여주는 태그) : ", viewCardEl);
516
517        clone.on('select', valueSelect);
518        clone.on("input", function (e) {
519          maskCardNumber(e);
520        });
521      }
522    })
523  }
524
525  /* ------ 생년월일(ex-> 950817) 마스킹 처리 start ------ */
526  let [frontRrnNoInputStart, frontRrnNoInputEnd] = [0, 0];// 입력 위치 확인하기위한 변수
527  let inMaskingFrontRrnOldVal = '';
528  /*마스킹 설정*/
529  let frontRrnMaskRange      = [2, 6];    // 마스킹 처리 범위[시작, 끝]
530  let frontRrnMaskText          = '*';       // 마스킹 처리할 문자
531
532// 중간 잘라내기
533  function frontRrnValueSlice(str, start, end, midStr){
534    const front = str.slice(0, start);
535    const back  = str.slice(end);
536    if (midStr) return front + midStr + back;
537    else return front + back;
538  }
539  function frontRrnValueSelect(e){
540    frontRrnNoInputStart = e.target.selectionStart;
541    frontRrnNoInputEnd   = e.target.selectionEnd;
542  }
543
544  let testNum = 1;
545  function maskFrontRrn(e, viewBirthDayTag, birthDayInputTag){
546    // maskedId = taxId.substring(0,2) + taxId.substring(2).replace(/[0-9a-zA-Z]/g, "*");
547    let viewFrontRrnEl = viewBirthDayTag ? viewBirthDayTag : '';
548    let realFrontRrnEl  = birthDayInputTag ? birthDayInputTag : '';
549
550    let data = e.originalEvent.data;     // 입력 데이터
551    const inputType = e.originalEvent.inputType;// 입력 방식
552    let position = e.target.selectionStart;// 입력 후 시작위치
553
554    // console.log('----------------- maskFrontRrn 메서드 실행!!! ------------------- ');
555    // console.log('>>> e : ', e);
556    // console.log('입력데이터 : ', data);
557    // // console.log('입력데이터 tpye : ', typeof data);
558    // console.log('입력 방식 : ', inputType);
559    // console.log('입력 후 시작위치 : ', position);
560    // console.log('frontRrnNoInputStart : ', frontRrnNoInputStart);
561    // console.log('frontRrnNoInputEnd : ', frontRrnNoInputEnd);
562    // console.log('입력 시 viewFrontRrnEl.value : ', viewFrontRrnEl.value);
563    // console.log('기존 realFrontRrnEl.value : ', realFrontRrnEl.value);
564    // console.log('입력 텍스트가 숫자 0~9 사이인지 check >> parseInt(data)  : ', parseInt(data));
565
566    //한글 입력 방지
567    e.target.value = e.target.value.replace(/[^0-9*]/g, '');
568
569    let regex = /[ㄱ-ㅎㅏ-ㅣ가-힣]/;
570    // if(regex.test(data)){
571    //   console.log('실행확인');
572    //   data = data.replace(regex,'');
573    // }
574
575    // console.log('e.nativeEvent : ', e.originalEvent);
576    // console.log('>>> 조합문자 작성중인가 ? >>> e.nativeEvent.isComposing : ', e.originalEvent.isComposing);
577    if(e.originalEvent.isComposing){
578      //조합문자 작성 중 종료처리
579      // e.originalEvent.isComposing = false;
580      // e.originalEvent.inputType = 'insertText';
581      //
582      // console.log('조합문자 작성 중 > e.target.value : ', e.target.value);
583      // console.log('조합문자 작성 중 > viewFrontRrnEl : ', viewFrontRrnEl);
584      // console.log('조합문자 작성 중 > inMaskingFrontRrnOldVal : ', inMaskingFrontRrnOldVal);
585      e.target.value = inMaskingFrontRrnOldVal;
586
587      // if(regex.test(e.target.value)){
588      //   console.log('실행확인');
589      //   console.log(testNum + '번째 실행');
590      //   testNum++;
591      //   e.target.value = e.target.value.slice(0,-1);
592      // }
593
594      // console.log('isComposing > true >> viewFrontRrnEl.value.replace 처리 : ', viewFrontRrnEl.value.replace(/[^0-9*]/g, ''));
595      // e.target.value = viewFrontRrnEl.value.replace(/[^0-9*]/g, '');
596
597
598
599      /* 이 부분에서 한글 입력시 지워지는 현상이 발생함 */
600      // console.log('조합문자 작성 중 > 한글 입력값 제거하고 마스킹문자 치환 전 realFrontRrnEl.value : ', realFrontRrnEl.value);
601      // const value = realFrontRrnEl.value;
602      // const splitValue = value.split('');
603      // console.log('조합문자 작성 중 > splitValue : ', splitValue);
604      // const maskValue = splitValue.map((v, i) => {
605      //   if (i >= parseInt(frontRrnMaskRange[0]) && i <= parseInt(frontRrnMaskRange[1])) return frontRrnMaskText;
606      //   else return v;
607      // }).join('');
608      // console.log('조합문자 작성 중 > maskValue : ', maskValue);
609      // // 치환 값 넣기
610      // e.target.value = maskValue;
611
612      return;
613    }
614
615    // 삭제
616    if(inputType === 'deleteContentBackward' || inputType === 'deleteContentForward'){
617      // 기본
618      if (frontRrnNoInputStart === frontRrnNoInputEnd) {
619        realFrontRrnEl.value = frontRrnValueSlice(realFrontRrnEl.value, position, position + 1);
620      }
621      // 드래그 삭제
622      else {
623        realFrontRrnEl.value = frontRrnValueSlice(realFrontRrnEl.value, frontRrnNoInputStart, frontRrnNoInputEnd);
624      }
625    }
626
627    // 입력
628    else if (parseInt(data) >= 0 && parseInt(data) <= 9){
629      if (frontRrnNoInputStart === frontRrnNoInputEnd) {
630        realFrontRrnEl.value = frontRrnValueSlice(realFrontRrnEl.value, position - 1, position - 1, data);
631      }
632      // 드래그 후 입력
633      else {
634        realFrontRrnEl.value = frontRrnValueSlice(realFrontRrnEl.value, frontRrnNoInputStart, frontRrnNoInputEnd, data);
635      }
636      // 위치저장
637      frontRrnNoInputStart = position;
638      frontRrnNoInputEnd   = position;
639    }
640
641    // console.log('frontRrnValueSlice리턴 >> realFrontRrnEl.value : ', realFrontRrnEl.value);
642
643    // console.log(maskRange);
644    let tempMaskRange = frontRrnMaskRange;
645
646    // console.log('입력 후 realFrontRrnEl.value : ', realFrontRrnEl.value);
647
648    const value = realFrontRrnEl.value;
649    const splitValue = value.split('');
650
651    // console.log('splitValue : ', splitValue);
652
653    const maskValue = splitValue.map((v, i) => {
654      if (i >= parseInt(tempMaskRange[0]) && i <= parseInt(tempMaskRange[1]) && v !== '-') return frontRrnMaskText;
655      else return v;
656    }).join('');
657    // 치환 값 넣기
658    e.target.value = maskValue;
659    inMaskingFrontRrnOldVal = maskValue;
660
661    // 바뀐 위치 원상복구
662    e.target.selectionStart = position;
663    e.target.selectionEnd   = position;
664
665    //실제로 db에 넘길 값 담아주기
666    realFrontRrnEl.value = realFrontRrnEl.value.replace(/[^0-9]/g, '');
667    // console.log('realFrontRrnEl.value : ', realFrontRrnEl.value);
668  }
669
670  $.fn.maskBirthDayText = function () {
671    $(this).each(function (idx, el) {
672      let birthDayInputTag = $(el);
673      let viewBirthDayTag = birthDayInputTag.clone();
674      viewBirthDayTag.attr({
675        "id" : "view-birthDay",
676        "name" : "view-birthDay",
677        "maxlength" : 6
678      });
679      viewBirthDayTag.removeAttr("data-birthDay-text-masking");
680      birthDayInputTag.parent().append(viewBirthDayTag);
681
682      birthDayInputTag.attr("type", "hidden");
683
684      viewBirthDayTag.on('select', frontRrnValueSelect);
685      viewBirthDayTag.on("input", function(e){
686        // console.log('birthDayInputTag : ', birthDayInputTag);
687        // console.log('viewBirthDayTag : ', viewBirthDayTag);
688
689        // console.log('!!!! >>> e.originalEvent.isComposing : ', e.originalEvent.isComposing);
690        // if(!e.originalEvent.isComposing){
691        //   e.preventDefault();
692        //   return;
693        // }
694
695        maskFrontRrn(e, viewBirthDayTag[0], birthDayInputTag[0]);
696      });
697    })
698  }
699  /* ------ 생년월일(ex-> 950817) 마스킹 처리 end ------ */
700
701  /* ------ 계좌번호 마스킹 처리 start ------ */
702  let [accountNoInputStart, accountNoInputEnd] = [0, 0];// 입력 위치 확인하기위한 변수
703  /*마스킹 설정*/
704  let accountNoMaskRange      = [-5];    // 마스킹 처리 범위[시작, 끝] >> 앞 3자리, 뒷 3자리 마스킹 처리
705  let accountNoMaskText          = '*';       // 마스킹 처리할 문자
706
707// 중간 잘라내기
708  function accountNoValueSlice(str, start, end, midStr){
709    const front = str.slice(0, start);
710    const back  = str.slice(end);
711    if (midStr) return front + midStr + back;
712    else return front + back;
713  }
714  function accountNoValueSelect(e){
715    accountNoInputStart = e.target.selectionStart;
716    accountNoInputEnd   = e.target.selectionEnd;
717  }
718  function maskAccountNo(e, viewAccountNoTag, accountNoInputTag){
719    // maskedId = taxId.substring(0,2) + taxId.substring(2).replace(/[0-9a-zA-Z]/g, "*");
720    let viewAccountNoEl = viewAccountNoTag ? viewAccountNoTag : '';
721    let realAccountNoEl  = accountNoInputTag ? accountNoInputTag : '';
722
723    const data = e.originalEvent.data;     // 입력 데이터
724    const inputType = e.originalEvent.inputType;// 입력 방식
725    let position = e.target.selectionStart;// 입력 후 시작위치
726
727    // console.log('>>> e : ', e);
728    // console.log('입력데이터 : ', data);
729    // console.log('입력 방식 : ', inputType);
730    // console.log('입력 후 시작위치 : ', position);
731    // console.log('frontRrnNoInputStart : ', accountNoInputStart);
732    // console.log('frontRrnNoInputEnd : ', accountNoInputEnd);
733    // console.log('입력 시 viewFrontRrnEl.value : ', viewAccountNoEl.value);
734    // console.log('기존 realAccountNoEl.value : ', realAccountNoEl.value);
735
736    // 삭제
737    if(inputType === 'deleteContentBackward' || inputType === 'deleteContentForward'){
738      // 기본
739      if (accountNoInputStart === accountNoInputEnd) {
740        realAccountNoEl.value = accountNoValueSlice(realAccountNoEl.value, position, position + 1);
741      }
742      // 드래그 삭제
743      else {
744        realAccountNoEl.value = accountNoValueSlice(realAccountNoEl.value, accountNoInputStart, accountNoInputEnd);
745      }
746    }
747
748    // 입력
749    else if (parseInt(data) >= 0 && parseInt(data) <= 9){
750      if (accountNoInputStart === accountNoInputEnd) {
751        realAccountNoEl.value = accountNoValueSlice(realAccountNoEl.value, position - 1, position - 1, data);
752      }
753      // 드래그 후 입력
754      else {
755        realAccountNoEl.value = accountNoValueSlice(realAccountNoEl.value, accountNoInputStart, accountNoInputEnd, data);
756      }
757      // 위치저장
758      accountNoInputStart = position;
759      accountNoInputEnd   = position;
760    }
761
762    // console.log('accountNoValueSlice리턴 >> realAccountNoEl.value : ', realAccountNoEl.value);
763
764    // console.log(maskRange);
765    let tempMaskRange = accountNoMaskRange;
766
767    // console.log('입력 후 realAccountNoEl.value : ', realAccountNoEl.value);
768
769    const value = realAccountNoEl.value;
770    const splitValue = value.split('');
771    const maskValue = splitValue.map((v, i) => {
772      if (i > 4 && i > (splitValue.length-1 + parseInt(tempMaskRange[0]))) return accountNoMaskText;
773      else return v;
774    }).join('');
775
776    // 치환 값 넣기
777    e.target.value = maskValue;
778
779    // 바뀐 위치 원상복구
780    e.target.selectionStart = position;
781    e.target.selectionEnd   = position;
782
783    //실제로 db에 넘길 값 담아주기
784    realAccountNoEl.value = realAccountNoEl.value.replace(/[^0-9]/g, '');
785    // console.log('realAccountNoEl.value : ', realAccountNoEl.value);
786  }
787
788  $.fn.maskAccountNoText = function () {
789    $(this).each(function (idx, el) {
790      let accountNoInputTag = $(el);
791      let viewAccountNoTag = accountNoInputTag.clone();
792      viewAccountNoTag.attr({
793        "id" : "view-accountNumber",
794        "name" : "view-accountNumber",
795        "maxlength" : 14
796      });
797      viewAccountNoTag.removeAttr("data-accountNumber-text-masking");
798      accountNoInputTag.parent().prepend(viewAccountNoTag);
799
800      accountNoInputTag.attr("type", "hidden");
801
802      viewAccountNoTag.on('select', accountNoValueSelect);
803      viewAccountNoTag.on("input", function(e){
804        maskAccountNo(e, viewAccountNoTag[0], accountNoInputTag[0]);
805      });
806    })
807  }
808  /* ------ 계좌번호 마스킹 처리 end ------ */
809
810  $(document).ready(function () {
811    $(this).find("[data-call-text]").callText();
812    $(this).find("[data-credit-text]").creditText();
813    $(this).find("[data-credit-text-masking]").maskCreditText();
814    $(this).find("[data-birthDay-text-masking]").maskBirthDayText();
815    $(this).find("[data-accountNumber-text-masking]").maskAccountNoText();
816  })
817
818  $.fn.serializeObject = function () {
819    var o = {};
820    var a = this.serializeArray();
821    $.each(a, function () {
822      if (o[this.name] !== undefined) {
823        if (!o[this.name].push) {
824          o[this.name] = [o[this.name]];
825        }
826        o[this.name].push(this.value || '');
827      } else {
828        o[this.name] = this.value || '';
829      }
830    });
831    return o;
832  };
833
834  $.fn.fddCarousel = function (options = {}) {
835    this.index = 0;
836    var jt = $(this);
837    this.items = jt.find('[data-carousel="items"]>*');
838    this.dots = jt.find('[data-carousel="dot"]>*');
839    this.current = jt.find('[data-carousel="current"]');
840
841    jt.find('[data-carousel="max"]').html(this.items.length);
842    this.current.html(this.index + 1);
843
844    this.applyTimeout = function (ms) {
845      if (this.tk) {
846        clearTimeout(this.tk);
847      }
848      this.tk = setTimeout(function () {
849        that.move(1);
850      }, ms);
851    }
852
853    this.onChangeIndex = function (ix) {
854      this.index = ix;
855      this.current.html(ix + 1);
856
857      this.items.removeClass("left");
858      this.items.removeClass("right");
859      this.items.removeClass("active");
860      this.dots.removeClass("active");
861
862      $(this.items.get(ix)).addClass("active");
863      $(this.dots.get(ix)).addClass("active");
864
865      var lix = ix - 1;
866      if (lix < 0) {
867        lix = this.items.length - 1;
868      }
869      var rix = ix + 1;
870      if (rix >= this.items.length) {
871        rix = 0;
872      }
873
874      $(this.items.get(lix)).addClass("left");
875      $(this.items.get(rix)).addClass("right");
876
877      this.applyTimeout(options.auto);
878
879    }.bind(this);
880    this.onChangeIndex(0);
881
882    this.move = function (gap) {
883      var ix = this.index + gap;
884      if (ix < 0) {
885        ix = this.items.length - 1;
886      } else if (ix >= this.items.length) {
887        ix = 0;
888      }
889      this.onChangeIndex(ix);
890    }.bind(this);
891
892    var that = this;
893    jt.find('[data-carousel="prev"]').click(function () {
894      that.move(-1)
895    });
896    jt.find('[data-carousel="next"]').click(function () {
897      that.move(1)
898    });
899
900    this.dots.on("click", function () {
901      var ix = that.dots.index(this);
902      if (ix - that.index != 0) {
903        that.move(ix - that.index)
904      }
905    });
906
907    if (options.activeClick) {
908      this.items.on("click", function (e) {
909        var ix = that.items.index(this);
910        if (ix != that.index) {
911          e.preventDefault();
912          that.move(ix - that.index)
913          return false;
914        }
915      })
916    }
917
918    if (options.auto) {
919      this.applyTimeout(options.auto);
920    }
921  }
922
923  $(document).on("click", "[data-toggle-view]", function () {
924    var target = $($(this).attr("data-toggle-view"));
925    if (target.css("display") === "none") {
926      target.css("display", "block")
927    } else {
928      target.css("display", "none")
929    }
930  })
931
932  $(document).on("click", "[data-require-login]", function (e) {
933    if (!window.is_signed) {
934      e.preventDefault();
935      location.href = "/member/login?redirectUrl=" + $(this).attr("href")
936    }
937  })
938
939  $(document).on("click", "#ftc_link", function (e) {
940    var h = $(this).attr("href");
941    var p = window.open(h, 'ftc_link', 'width=750,height=700');
942    p.focus();
943
944    e.preventDefault();
945    return false;
946  })
947
948  window.alert = function (message, callback, okBtnText) {
949    $("#alertModalLabel").html("");
950    $("#alertModal .modal-body").html(message);
951    $("#alertModal").modal('show');
952    if (okBtnText) {
953      $("#alertModal").find('.modal-footer').text(okBtnText);
954    }
955    if (callback && typeof callback == 'function') {
956      $("#alertModal .modal-footer").on("click", function () {
957        $("#alertModal").find('.modal-footer').text('확인');
958        callback();
959        $("#alertModal .modal-footer").off("click")
960
961      });
962    }
963      $("#alertModal").on("hide.bs.modal", function () {
964        $('#alertModal .modal-footer').removeClass('disabled')
965        $('#alertModal .modal-footer').prop('disabled',false);
966        $("#alertModal .modal-footer").off("click")
967        $("#alertModal").find('.modal-footer').text('확인');
968        clearTimeout(timer)
969      });
970  }
971
972  window.alertAndAlwaysRedirect = function (message, callback, okBtnText) {
973    $("#alertModalLabel").html("");
974    $("#alertModal .modal-body").html(message);
975    $("#alertModal").modal('show');
976    if (okBtnText) {
977      $("#alertModal").find('.modal-footer').text(okBtnText);
978    }
979    if (callback && typeof callback == 'function') {
980      $("#alertModal .modal-footer").on("click", function () {
981        $("#alertModal").find('.modal-footer').text('확인');
982        $("#alertModal .modal-footer").off("click")
983
984      });
985    }
986    $("#alertModal").on("hide.bs.modal", function () {
987      $('#alertModal .modal-footer').removeClass('disabled')
988      $('#alertModal .modal-footer').prop('disabled',false);
989      $("#alertModal .modal-footer").off("click")
990      $("#alertModal").find('.modal-footer').text('확인');
991      clearTimeout(timer)
992      // 모달창 꺼졌을 때 redirect하는 부분
993      callback();
994    });
995  }
996
997  window.alertWithRedirect = function (message, uri) {
998    window.alert(message, function () {
999      location.href = uri;
1000    })
1001  };
1002
1003  window.confirmDesign = function (title, message, callback, option) {
1004    const body = {
1005        title: !message ? '' : title,
1006      content: !message ? title : message,
1007    };
1008
1009    $("#confirmModal .modal-title").html(body.title);
1010    $("#confirmModal .modal-body").html(body.content);
1011    $("#confirmModal").modal('show');
1012
1013    $("#confirmModal .confirm").off("click")
1014    if (callback&& typeof callback =='function') {
1015      $("#confirmModal .confirm").on("click", function () {
1016        callback()
1017        $("#confirmModal .confirm").off("click")
1018        $("#confirmModal").modal('hide');
1019      })
1020    }
1021  };
1022
1023  window.confirmDesign2 = function (title, message, callback,failCallback, option) {
1024    const body = {
1025      title: !message ? '' : title,
1026      content: !message ? title : message,
1027    };
1028
1029    $("#confirmModal2").removeClass("show");
1030    $("#confirmModal2").css("display","none");
1031
1032    $("#confirmModal2 .modal-title").html(body.title);
1033    $("#confirmModal2 .modal-body").html(body.content);
1034    $("#confirmModal2").addClass("show");
1035    $("#confirmModal2").css("display","block");
1036
1037
1038
1039    if (callback&& typeof callback =='function') {
1040      $("#confirmModal2 .confirm").on("click", function () {
1041        callback()
1042        $("#confirmModal2 .confirm").off("click")
1043        //$("#confirmModal2").modal('hide');
1044        $("#confirmModal2").removeClass("show");
1045        $("#confirmModal2").css("display","none");
1046      })
1047    }
1048
1049    if (failCallback&& typeof failCallback =='function') {
1050      $("#confirmModal2 .cancel").on("click", function (){
1051        $("#confirmModal2").removeClass("show");
1052        $("#confirmModal2").css("display","none");
1053        failCallback()
1054      });
1055
1056      $("#confirmModal2 .close").on("click", function (){
1057        $("#confirmModal2").removeClass("show");
1058        $("#confirmModal2").css("display","none");
1059        failCallback()
1060      });
1061
1062    }
1063
1064    if(option?.hideOnBackgroundClick === true) {
1065      $("#confirmModal2").on("click", function (e) {
1066        // 클릭한 요소가 모달 내용 영역(.modal-content)가 아니라면 (즉, 배경 클릭 시)
1067        if (!$(e.target).closest(".modal-content").length) {
1068          // 모달 숨기기
1069          $(this).removeClass("show");
1070          $(this).css("display", "none");
1071        }
1072      });
1073    }
1074  };
1075
1076    window.confirmDesign_v2 = function (title, message, callback, option) {
1077        const opt = $.extend(
1078            {
1079                // confirm 버튼 연타 방지용 락 (짧게)
1080                clickLockMs: 1200,
1081
1082                // 콜백이 비정상(에러/멈춤)일 때, confirm 버튼 잠금이 영원히 풀리지 않는 상황 방지
1083                // 필요 없으면 0 또는 null로 꺼도 됨
1084                failSafeMs: 0,
1085
1086                // 콜백 호출 후 모달 자동 닫기
1087                autoHide: true
1088            },
1089            option || {}
1090        );
1091
1092        const body = {
1093            title: !message ? "" : title,
1094            content: !message ? title : message
1095        };
1096
1097        const $modal = $("#confirmModal");
1098        const $btn = $modal.find(".confirm");
1099
1100        $modal.find(".modal-title").html(body.title);
1101        $modal.find(".modal-body").html(body.content);
1102
1103        // 모달 show
1104        $modal.modal("show");
1105
1106        // ✅ 핵심: 이전 click 핸들러 싹 제거(공통 confirmDesign과 동일한 패턴)
1107        $btn.off("click");
1108
1109        // ✅ 연타 방지 상태
1110        let locked = false;
1111        let lockTimer = null;
1112        let failSafeTimer = null;
1113
1114        function unlock() {
1115            locked = false;
1116            $btn.prop("disabled", false);
1117            if (lockTimer) { clearTimeout(lockTimer); lockTimer = null; }
1118            if (failSafeTimer) { clearTimeout(failSafeTimer); failSafeTimer = null; }
1119        }
1120
1121        // 모달이 닫히면 무조건 잠금 해제(취소/X/백버튼 등 포함)
1122        $modal.off("hidden.bs.modal.confirmDesign3").on("hidden.bs.modal.confirmDesign3", function () {
1123            unlock();
1124            // 이 모달에서 confirm 버튼에 붙인 핸들러도 정리
1125            $btn.off("click");
1126        });
1127
1128        if (callback && typeof callback === "function") {
1129            $btn.on("click", function () {
1130                // ✅ confirm 버튼 연타 차단
1131                if (locked) return;
1132                locked = true;
1133                $btn.prop("disabled", true);
1134
1135                // 짧은 클릭락 (연타 방지)
1136                if (opt.clickLockMs && opt.clickLockMs > 0) {
1137                    lockTimer = setTimeout(function () {
1138                        // 콜백이 “요청 전송 플래그”로 막고 있다면 이 unlock은 큰 의미 없고,
1139                        // confirm 버튼 UI만 다시 활성화되는 역할
1140                        unlock();
1141                    }, opt.clickLockMs);
1142                }
1143
1144                // fail-safe (선택)
1145                if (opt.failSafeMs && opt.failSafeMs > 0) {
1146                    failSafeTimer = setTimeout(function () {
1147                        unlock();
1148                    }, opt.failSafeMs);
1149                }
1150
1151                try {
1152                    callback();
1153                } finally {
1154                    if (opt.autoHide) $modal.modal("hide");
1155                }
1156            });
1157        }
1158    };
1159
1160
1161  window.confirmDesign2Promise = function (title, message, callback, failCallback, option) {
1162    return new Promise((resolve, reject) => {
1163      const body = {
1164        title: !message ? '' : title,
1165        content: !message ? title : message,
1166      };
1167
1168      $("#confirmModal2").removeClass("show").css("display", "none");
1169
1170      $("#confirmModal2 .modal-title").html(body.title);
1171      $("#confirmModal2 .modal-body").html(body.content);
1172
1173      if (option?.confirmText) {
1174        $("#confirmModal2 .modal-footer .confirm").text(option.confirmText);
1175      }
1176
1177      if (option?.cancelText) {
1178        $("#confirmModal2 .modal-footer .cancel").text(option.cancelText);
1179      }
1180
1181      $("#confirmModal2").addClass("show").css("display", "block");
1182
1183      const confirmHandler = () => {
1184        $("#confirmModal2 .confirm").off("click", confirmHandler);
1185        $("#confirmModal2").removeClass("show").css("display", "none");
1186        resolve('confirm');
1187      };
1188
1189      const cancelHandler = () => {
1190        $("#confirmModal2 .cancel").off("click", cancelHandler);
1191        $("#confirmModal2 .close").off("click", cancelHandler);
1192        $("#confirmModal2").removeClass("show").css("display", "none");
1193        resolve('cancel');
1194      };
1195
1196      $("#confirmModal2 .confirm").on("click", confirmHandler);
1197      $("#confirmModal2 .cancel").on("click", cancelHandler);
1198      $("#confirmModal2 .close").on("click", cancelHandler);
1199    });
1200  };
1201
1202
1203  $(document).on("click", "[data-cart-type][data-cart-id]", function (e) {
1204    var that = $(this);
1205    var type = that.attr("data-cart-type");
1206    var id = that.attr("data-cart-id");
1207    var eventIdx = that.attr("data-cart-event");
1208
1209    var section = that.attr("data-section");
1210    var recommend_type = that.attr("data-recommend_type");
1211    var pushData = new Object();
1212    pushData.section = section == undefined ? "" : section;
1213    pushData.recommend_type = recommend_type == undefined ? "" : recommend_type;
1214
1215    if(section == "product"){
1216      var activeText = "메인페이지";
1217      if($(".breadcrumb-style").length > 0){
1218        if($(".breadcrumb-style").find("li").length < 3){
1219          activeText = "전체";
1220        } else {
1221          activeText = $(".breadcrumb-style").find("li").eq(2).find("a").text();
1222        }
1223      };
1224
1225      if($(".nav-tabs").length > 0){
1226        if(type == "daily"){
1227          activeText = "매일배송 하단 상품 추천";
1228        } else {
1229          activeText = "택배배송 하단 상품 추천";
1230        }
1231      }
1232      pushData.section = activeText;
1233    }
1234
1235
1236    if (id && type) {
1237      addCart(type, id, { eventIdx }, pushData);
1238    }
1239    e.preventDefault();
1240    return false;
1241  });
1242
1243  $(document).on("click", "[data-buy-type='box'][data-buy-id]", function (e) {
1244
1245    var that = $(this);
1246    var itemCode = that.attr("data-buy-id");
1247    var eventIdx = that.attr("data-buy-event");
1248
1249    var args = { item: [{itemCode, qty: "1", eventIdx }] };
1250    location.href = "/order/box/step1?item=" + encodeURIComponent(JSON.stringify(args));
1251
1252    e.preventDefault();
1253    return false;
1254  });
1255
1256  $(document).on("click", "[data-wish-type][data-wish-id]", function (e) {
1257    var that = $(this);
1258    var type = that.attr("data-wish-type");
1259    var id = that.attr("data-wish-id");
1260
1261    if (!window.is_signed) {
1262      alert("로그인 후 찜한상품으로 담을 수 있습니다.", function () {
1263        location.href = "/member/login?redirectUrl=" + encodeURIComponent(location.href);
1264      });
1265      return false;
1266    }
1267
1268    axios.post('/product/' + type + '/interest/' + id).then(function ({data}) {
1269      if (!data.ok) {
1270        return;
1271      }
1272      console.log(that, that.hasClass("active"))
1273      if (!that.hasClass("active")) {
1274        $("[data-wish-type][data-wish-id='" + id + "']").addClass('active');
1275        alert("찜한상품에 담겼습니다.");
1276      } else {
1277        $("[data-wish-type][data-wish-id='" + id + "']").removeClass('active');
1278        alert("찜한상품이 해제되었습니다.");
1279      }
1280    }).catch(function (e) {
1281      alert("서버와 연결이 올바르지 않습니다.");
1282    })
1283
1284    // addCarts(type, id, {});
1285
1286    e.preventDefault();
1287    return false;
1288  });
1289
1290
1291  $(document).on("click", "[data-product-preview]", function (e) {
1292    var that = $(this);
1293    var id = parseInt(that.attr("data-product-preview"), 10);
1294    if (id) {
1295      $("#productPreviewModal .modal-content").html("");
1296      $("#productPreviewModal").addClass("loading").modal('show');
1297      $("#productPreviewModal .modal-content").load("/product/preview/" + id, function () {
1298        $("#productPreviewModal").removeClass("loading");
1299      });
1300    }
1301    e.preventDefault();
1302    return false;
1303  });
1304
1305  window.shareToKakao = function (title, uri) {
1306    if (window.kakaoShareData) {
1307      Kakao.Share.sendCustom({
1308        templateId: window.kakaoShareData.key,
1309        templateArgs: window.kakaoShareData.data,
1310      });
1311      return;
1312    }
1313    var requestUrl = uri || location.href;
1314    if (requestUrl.substring(0, 1) === "/") {
1315      requestUrl = location.origin + requestUrl;
1316    }
1317    Kakao.Share.sendScrap({
1318      requestUrl: requestUrl,
1319      installTalk: true,
1320    });
1321  }
1322
1323  $(document).ready(function () {
1324    var clipboard = new ClipboardJS('[data-clipboard-target]');
1325    clipboard.on('success', function(e) {
1326      e.clearSelection();
1327      $("#shareModal").modal("hide");
1328      alert("URL 복사되었습니다");
1329    });
1330  });
1331
1332  $(document).ready(function () {
1333    if (window.pagableRender) {
1334      window.pagableRender(this);
1335    }
1336  });
1337
1338  $(document).on("click", "[data-list-more]", function (e) {
1339    var that = $(this);
1340    var selector = that.attr("data-list-more");
1341    var nextPage = that.attr("data-param");
1342    var listTarget = $(selector);
1343
1344    var path = location.origin + location.pathname;
1345    var qs = location.search.substring(1).split("&");
1346    path += "?pageNo=" + nextPage;
1347
1348    $.each(qs, function (i, str) {
1349      if (str == "") return;
1350      if (str.indexOf("pageNo=") != 0) {
1351        path += "&";
1352        path += str;
1353      }
1354    });
1355
1356    listTarget.addClass("page-loading");
1357    $("#loading").modal("show");
1358    axios.get(path).then(function (res) {
1359      var patt = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi;
1360
1361      var html = res.data;
1362      html = html.substring(html.indexOf("<body"));
1363      html = html.substring(0, html.indexOf("</body"));
1364      html = html.replace(patt, '');
1365      html = html.trim();
1366
1367      var result = $(html);
1368      var items = result.find(selector + " > *");
1369
1370      var pagination = result.find("[data-pagination]");
1371      if (pagination.length > 0) {
1372        // 페이지네이션일때
1373        that.parents("[data-pagination]").html(pagination.get(0).innerHTML);
1374      }
1375      else {
1376        // 더보기 일때
1377        var moreNext = result.find("[data-list-more]");
1378        if (!moreNext.length) {
1379          // 다음페이지가 빈거라면
1380          that.remove();
1381        } else {
1382          // 다음페이지 파라미터 가져다 붙이기
1383          that.attr("data-param", moreNext.attr("data-param"));
1384        }
1385      }
1386
1387      if (listTarget.attr("data-list-object") === "append") {
1388        listTarget.append(items);
1389      }
1390      else {
1391        listTarget.html(items);
1392        $(document).scrollTop(listTarget.offset().top);
1393        history.replaceState({}, "", path);
1394      }
1395      listTarget.removeClass("page-loading");
1396      if (window.pagableRender) {
1397        window.pagableRender(listTarget);
1398      }
1399    }).catch(function (e) {
1400      listTarget.removeClass("page-loading");
1401      that.remove();
1402    }).then(function () {
1403      $("#loading").modal("hide");
1404    });
1405
1406  });
1407
1408  $(document).on("click", ".btn-question:has(.show-body)", function (e) {
1409    var html = $(".show-body", this).html();
1410    $("#descModal .modal-title").text($(".show-body", this).attr("title"));
1411    $("#descModal .modal-body").html(html);
1412    $("#descModal").modal("show");
1413  })
1414
1415  $(document).on('keyup blur','[data-receiver]',function (){
1416    const regex = /[^ㄱ-ㅎ|가-힣|a-z|A-Z|0-9|]+$/;
1417    $(this).val($(this).val().replace(regex,''))
1418  })
1419
1420
1421})(jQuery);
1422
1423// 세자리 수 마다 (,) 처리
1424var numberFormat = function(num) {
1425  return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1426}
1427
1428function getDefaultClientId(){
1429  var custId= '';
1430
1431  $.ajax({
1432    type: 'POST',
1433    async : false,
1434    url: '/getCustomerNumber',
1435    error: function (request, status, error) {
1436      console.log("code:" + request.status + "\n" + "message:" + request.responseText + "\n" + "error:" + error);
1437    },
1438    success: function (result) {
1439      if(result.RESULT_MSG){
1440        custId = result.RESULT_MSG.data.custnumber;
1441      }
1442    }
1443  });
1444
1445  return custId;
1446}
1447function getDefaultOrderId(){
1448  var orderId = '';
1449
1450  $.ajax({
1451    type: 'POST',
1452    async : false,
1453    url: '/getCustomerNumber',
1454    error: function (request, status, error) {
1455      console.log("code:" + request.status + "\n" + "message:" + request.responseText + "\n" + "error:" + error);
1456    },
1457    success: function (result) {
1458      if(result.RESULT_MSG){
1459        orderId = result.RESULT_MSG.data.prtnId;
1460      }
1461    }
1462  });
1463
1464  return orderId;
1465}
1466
1467function getDefaultCustDrinkInfo(){
1468  let defaultCustDrinkInfo = {
1469    custnumber : '',
1470    prtnId : '',
1471    isDefaultNotOnlyPrepayment : '',
1472  };
1473
1474  $.ajax({
1475    type: 'POST',
1476    async : false,
1477    url: '/getDefaultCustDrinkInfo',
1478    error: function (request, status, error) {
1479      console.log("code:" + request.status + "\n" + "message:" + request.responseText + "\n" + "error:" + error);
1480    },
1481    success: function (result) {
1482      if(result.RESULT_MSG){
1483        // console.log('result.RESULT_MSG : ', result.RESULT_MSG);
1484        defaultCustDrinkInfo.custnumber = result.RESULT_MSG.data.custnumber ?? '';
1485        defaultCustDrinkInfo.prtnId = result.RESULT_MSG.data.prtnId ?? '';
1486        defaultCustDrinkInfo.isDefaultNotOnlyPrepayment = result.RESULT_MSG.isNotOnlyPrepayment;
1487      }
1488    }
1489  });
1490
1491  return defaultCustDrinkInfo;
1492}
1493
1494/* getIsMobile : 앱에서 구동중인지 여부 반환 */
1495function getIsMobile() {
1496  var uagent = navigator.userAgent.toLowerCase();
1497  // var uagent = 'mozila/5.0 (ipad; cpu os 17_0 like mac os x) applewebkit/605.115(khtml, like gecko) mobile/15e148ios_app';
1498  var isMobile = false;
1499
1500  if (uagent.search("android") > -1) {
1501    // if (uagent.search("build") > -1 && uagent.search("naver") <= -1) {
1502    //   isMobile = true;
1503    // }
1504    if (uagent.search("android_app") > -1) {
1505      isMobile = true;
1506    }
1507  } else if (uagent.search("iphone") > -1 || uagent.search("ipad") > -1 || uagent.indexOf("ipod") > -1) {
1508    // if (uagent.search("safari") <= -1) {
1509    //   isMobile = true;
1510    // }
1511    if (uagent.search("ios_app") > -1) {
1512      isMobile = true;
1513    }
1514  }
1515
1516  return isMobile;
1517}
1518
1519function safeDataLayerPush(data) {
1520  try {
1521    window.dataLayer = window.dataLayer || [];
1522
1523    if (!data || typeof data !== 'object') {
1524      throw new Error('유효하지 않은 데이터입니다. 객체를 전달해야 합니다.');
1525    }
1526
1527    window.dataLayer.push(data);
1528  } catch (error) {
1529    console.error('safeDataLayerPush 오류:', error);
1530  }
1531}
1532
1533/**
1534 * 샵풀 구독 고객번호인지 확인 후 redirect
1535 * @param {string} custNumber
1536 * @param {string} prtnId
1537 * @param {string} type - 페이지구분값 (음용상세페이지, 상품변경페이지)
1538 */
1539function checkCustNumberBeforeRedirect(custNumber, prtnId, type) {
1540
1541  $.ajax({
1542    url: "/mypage/drink/check/shoppul-subscript-custnumber",
1543    type: "GET",
1544    data: {custNumber : custNumber,
1545      prtnId : prtnId
1546    },
1547    dataType: "json",
1548    success: function(response) {
1549      if (response.RESULT_MSG === 'shoppulSubscript') {
1550        alert('해당 주문내역은 #풀무원에서 확인해주세요.');
1551        return false;
1552      }
1553
1554      if(type === 'drinkDetail') {
1555        location.href = '/mypage/drink/drinks/' + custNumber + '/' + prtnId;
1556      } else if (type === 'drinkChange') {
1557        location.href = '/mypage/drink/drink/change/' + custNumber + '/' + prtnId;
1558      }
1559
1560    },
1561    error: function(request, status, error) {
1562      console.log("code:" + request.status + "\n" + "message:" + request.responseText + "\n" + "error:" + error);
1563    }
1564  });
1565
1566}
1567
1568

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.