PageSourceSearch

https://bukatsunavi.com/wp-content/themes/bukatsu/js/prjutils.js?ver=20260930175425

js bukatsunavi.com collected 2026-10-01 12:32:36 UTC 278,693 bytes, 7,798 lines download raw bytes

1// var $ = jQuery;
2
3(function (global) {
4  'use strict';
5
6  function parseImageCropRatio(ratio) {
7    if (typeof ratio === 'number' && isFinite(ratio) && ratio > 0) {
8      return ratio;
9    }
10    if (typeof ratio === 'string') {
11      let parts = ratio.split(':');
12      if (parts.length === 2) {
13        let width = Number(parts[0]);
14        let height = Number(parts[1]);
15        if (isFinite(width) && isFinite(height) && width > 0 && height > 0) {
16          return width / height;
17        }
18      }
19    }
20    if (ratio && typeof ratio === 'object') {
21      let ratioWidth = Number(ratio.width);
22      let ratioHeight = Number(ratio.height);
23      if (isFinite(ratioWidth) && isFinite(ratioHeight) && ratioWidth > 0 && ratioHeight > 0) {
24        return ratioWidth / ratioHeight;
25      }
26    }
27    return NaN;
28  }
29
30  function CommonImageCropper(image, options) {
31    if (!(image instanceof HTMLImageElement)) {
32      throw new TypeError('CommonImageCropper requires an HTMLImageElement.');
33    }
34    if (typeof global.Cropper !== 'function') {
35      throw new Error('Cropper.js is not loaded.');
36    }
37    this.image = image;
38    this.options = options || {};
39    this.cropper = null;
40    this.cropAction = null;
41    this.adjustingAspectRatio = false;
42    this.init();
43  }
44
45  CommonImageCropper.prototype.init = function () {
46    let self = this;
47    let cropperOptions = Object.assign({
48      viewMode: 1,
49      dragMode: 'move',
50      autoCropArea: 1,
51      responsive: true,
52      background: false
53    }, this.options.cropperOptions || {});
54    let aspectRatio = parseImageCropRatio(this.options.aspectRatio);
55    if (!isNaN(aspectRatio)) {
56      cropperOptions.aspectRatio = aspectRatio;
57    }
58    let originalCrop = cropperOptions.crop;
59    let originalCropmove = cropperOptions.cropmove;
60    cropperOptions.cropmove = function (event) {
61      self.cropAction = event.detail ? event.detail.action : null;
62      if (typeof originalCropmove === 'function') {
63        originalCropmove.call(this, event);
64      }
65    };
66    cropperOptions.crop = function (event) {
67      if (typeof originalCrop === 'function') {
68        originalCrop.call(this, event);
69      }
70      if (self.options.hardAspectRatioLimit && !self.adjustingAspectRatio && self.cropper) {
71        let adjustedData = self.clampAspectRatio(event.detail, self.cropAction);
72        if (adjustedData) {
73          self.adjustingAspectRatio = true;
74          self.cropper.setData(adjustedData);
75          self.adjustingAspectRatio = false;
76          return;
77        }
78      }
79      if (typeof self.options.onAspectRatioChange === 'function') {
80        self.options.onAspectRatioChange(self.getAspectRatioStatus(event.detail));
81      }
82    };
83    this.cropper = new global.Cropper(this.image, cropperOptions);
84    return this;
85  };
86
87  CommonImageCropper.prototype.setAspectRatio = function (ratio) {
88    let aspectRatio = parseImageCropRatio(ratio);
89    if (isNaN(aspectRatio)) {
90      throw new TypeError('The aspect ratio must be a positive number, "width:height", or { width, height }.');
91    }
92    this.cropper.setAspectRatio(aspectRatio);
93    return this;
94  };
95
96  CommonImageCropper.prototype.getData = function (rounded) {
97    return this.cropper.getData(Boolean(rounded));
98  };
99
100  CommonImageCropper.prototype.getCroppedCanvas = function (options) {
101    return this.cropper.getCroppedCanvas(options || {});
102  };
103
104  CommonImageCropper.prototype.getAspectRatioStatus = function (data) {
105    data = data || this.getData();
106    let width = Number(data.width);
107    let height = Number(data.height);
108    let ratio = width > 0 && height > 0 ? width / height : NaN;
109    let min = parseImageCropRatio(this.options.minAspectRatio);
110    let max = parseImageCropRatio(this.options.maxAspectRatio);
111    let tolerance = 0.001;
112    let valid = !isNaN(ratio)
113      && (isNaN(min) || ratio >= min - tolerance)
114      && (isNaN(max) || ratio <= max + tolerance);
115
116    return {
117      ratio: ratio,
118      min: min,
119      max: max,
120      valid: valid
121    };
122  };
123
124  CommonImageCropper.prototype.isAspectRatioValid = function () {
125    return this.getAspectRatioStatus().valid;
126  };
127
128  CommonImageCropper.prototype.clampAspectRatio = function (data, action) {
129    let status = this.getAspectRatioStatus(data);
130    if (status.valid || isNaN(status.ratio)) {
131      return null;
132    }
133
134    let targetRatio = status.ratio < status.min ? status.min : status.max;
135    if (isNaN(targetRatio)) {
136      return null;
137    }
138
139    let adjusted = {
140      x: Number(data.x),
141      y: Number(data.y),
142      width: Number(data.width),
143      height: Number(data.height),
144      rotate: Number(data.rotate) || 0,
145      scaleX: Number(data.scaleX) || 1,
146      scaleY: Number(data.scaleY) || 1
147    };
148    let movesHorizontally = action && (action.indexOf('e') !== -1 || action.indexOf('w') !== -1);
149    let movesVertically = action && (action.indexOf('n') !== -1 || action.indexOf('s') !== -1);
150
151    if (status.ratio < targetRatio) {
152      if (movesVertically && !movesHorizontally) {
153        let previousHeight = adjusted.height;
154        adjusted.height = adjusted.width / targetRatio;
155        if (action.indexOf('n') !== -1) {
156          adjusted.y += previousHeight - adjusted.height;
157        }
158      } else {
159        let previousWidth = adjusted.width;
160        adjusted.width = adjusted.height * targetRatio;
161        if (action && action.indexOf('w') !== -1) {
162          adjusted.x += previousWidth - adjusted.width;
163        }
164      }
165    } else {
166      if (movesHorizontally && !movesVertically) {
167        let previousWidth = adjusted.width;
168        adjusted.width = adjusted.height * targetRatio;
169        if (action.indexOf('w') !== -1) {
170          adjusted.x += previousWidth - adjusted.width;
171        }
172      } else {
173        let previousHeight = adjusted.height;
174        adjusted.height = adjusted.width / targetRatio;
175        if (action && action.indexOf('n') !== -1) {
176          adjusted.y += previousHeight - adjusted.height;
177        }
178      }
179    }
180
181    return adjusted;
182  };
183
184  CommonImageCropper.prototype.toBlob = function (options) {
185    let settings = options || {};
186    let canvas = this.getCroppedCanvas(settings.canvasOptions);
187    return new Promise(function (resolve, reject) {
188      canvas.toBlob(function (blob) {
189        if (blob) {
190          resolve(blob);
191        } else {
192          reject(new Error('Failed to create the cropped image.'));
193        }
194      }, settings.type || 'image/jpeg', settings.quality === undefined ? 0.92 : settings.quality);
195    });
196  };
197
198  CommonImageCropper.prototype.destroy = function () {
199    if (this.cropper) {
200      this.cropper.destroy();
201      this.cropper = null;
202    }
203  };
204
205  CommonImageCropper.getMaxCropData = function (imageWidth, imageHeight, minAspectRatio, maxAspectRatio) {
206    imageWidth = Number(imageWidth);
207    imageHeight = Number(imageHeight);
208    if (!isFinite(imageWidth) || !isFinite(imageHeight) || imageWidth <= 0 || imageHeight <= 0) {
209      throw new TypeError('Image width and height must be positive numbers.');
210    }
211
212    let min = parseImageCropRatio(minAspectRatio);
213    let max = parseImageCropRatio(maxAspectRatio);
214    if (!isNaN(min) && !isNaN(max) && min > max) {
215      throw new RangeError('The minimum aspect ratio must not exceed the maximum aspect ratio.');
216    }
217
218    let imageAspectRatio = imageWidth / imageHeight;
219    let cropWidth = imageWidth;
220    let cropHeight = imageHeight;
221    let cropX = 0;
222    let cropY = 0;
223
224    if (!isNaN(min) && imageAspectRatio < min) {
225      cropHeight = imageWidth / min;
226      cropY = (imageHeight - cropHeight) / 2;
227    } else if (!isNaN(max) && imageAspectRatio > max) {
228      cropWidth = imageHeight * max;
229      cropX = (imageWidth - cropWidth) / 2;
230    }
231
232    return {
233      x: cropX,
234      y: cropY,
235      width: cropWidth,
236      height: cropHeight
237    };
238  };
239
240  CommonImageCropper.parseRatio = parseImageCropRatio;
241  global.CommonImageCropper = CommonImageCropper;
242})(window);
243
244var prjutils = {
245  DaysOfWeek : [
246    "sunday",
247    "monday",
248    "tuesday",
249    "wednesday",
250    "thursday",
251    "friday",
252    "saturday"
253  ],
254  CompressOption: {
255    maxSizeMB: 2, // 最大サイズ(MB)
256    maxWidthOrHeight: 1920 //縦or横最大サイズ(px)
257  },
258  // アップロード画像ファイルの縮小版格納用(プレビュー時に縮小格納)
259  uploadFiles: {},
260  uploadFileIndex: 1,
261  isSaving: false,
262  ImageFileTypes: '.jpg, .jpeg, .png, .gif, .bmp, .ico, .svg, .heic, .heif',
263  HeicConverterUrl: '/themes/bukatsu/js/vendor/heic-to/heic-to.js',
264  heicConverterPromise: null,
265  // HEIC/HEIFはブラウザで直接扱えないため、圧縮前にJPEGへ変換する。
266  isHeicImage: function(file) {
267    if (!file) return false;
268    let fileName = (file.name || '').toLowerCase();
269    let mimeType = (file.type || '').toLowerCase();
270    return fileName.endsWith('.heic') || fileName.endsWith('.heif') ||
271      mimeType === 'image/heic' || mimeType === 'image/heif' ||
272      mimeType === 'image/heic-sequence' || mimeType === 'image/heif-sequence';
273  },
274  loadHeicConverter: function() {
275    if (typeof HeicTo === 'function') return Promise.resolve();
276    if (prjutils.heicConverterPromise) return prjutils.heicConverterPromise;
277    prjutils.heicConverterPromise = new Promise(function(resolve, reject) {
278      let script = document.createElement('script');
279      let contentUrl = (typeof binds !== 'undefined' && binds['contenturl']) ? binds['contenturl'] : '/wp-content';
280      script.src = contentUrl + prjutils.HeicConverterUrl;
281      script.async = true;
282      script.onload = function() {
283        if (typeof HeicTo === 'function') {
284          resolve();
285        } else {
286          prjutils.heicConverterPromise = null;
287          reject(new Error('HEIC画像変換ライブラリを読み込めませんでした。'));
288        }
289      };
290      script.onerror = function() {
291        prjutils.heicConverterPromise = null;
292        reject(new Error('HEIC画像変換ライブラリを読み込めませんでした。'));
293      };
294      document.head.appendChild(script);
295    });
296    return prjutils.heicConverterPromise;
297  },
298  // 拡張子がHEICでも、ブラウザ側ですでにJPEG/PNG等へ変換されている場合がある。
299  normalizeBrowserImageFile: function(file) {
300    let extensions = {
301      'image/jpeg': '.jpg',
302      'image/png': '.png',
303      'image/gif': '.gif',
304      'image/webp': '.webp',
305      'image/bmp': '.bmp'
306    };
307    let extension = extensions[(file.type || '').toLowerCase()];
308    if (!extension) return file;
309    let fileName = (file.name || 'image').replace(/\.(heic|heif)$/i, extension);
310    return new File([file], fileName, {
311      type: file.type,
312      lastModified: file.lastModified || Date.now()
313    });
314  },
315  convertHeicToJpeg: async function(file) {
316    if (!prjutils.isHeicImage(file)) return file;
317    await prjutils.loadHeicConverter();
318    let isActualHeic;
319    try {
320      isActualHeic = await HeicTo.isHeic(file);
321    } catch (error) {
322      console.error('HEIC image detection failed.', error);
323      throw new Error('画像形式を確認できませんでした。');
324    }
325    if (!isActualHeic) {
326      let normalizedFile = prjutils.normalizeBrowserImageFile(file);
327      if (normalizedFile !== file) return normalizedFile;
328      throw new Error('ファイルの拡張子と画像形式が一致していません。');
329    }
330    let converted;
331    try {
332      converted = await HeicTo({
333        blob: file,
334        type: 'image/jpeg',
335        quality: 0.9
336      });
337    } catch (error) {
338      console.error('HEIC image conversion failed.', error);
339      throw new Error('HEIC画像をJPEGに変換できませんでした。');
340    }
341    let jpegBlob = converted;
342    if (!jpegBlob) throw new Error('HEIC画像をJPEGに変換できませんでした。');
343    let jpegName = (file.name || 'image.heic').replace(/\.(heic|heif)$/i, '.jpg');
344    return new File([jpegBlob], jpegName, {
345      type: 'image/jpeg',
346      lastModified: file.lastModified || Date.now()
347    });
348  },
349  // ファイルを縮小する
350  // file element ファイルinput
351  compressImage: async function(file) {
352    let $file = $(file);
353    let sourceFile = await prjutils.convertHeicToJpeg(file.files[0]);
354    let compressBlob = await imageCompression(sourceFile, prjutils.CompressOption);
355    let compressFile = new File([compressBlob], sourceFile.name, {
356      type: compressBlob.type || sourceFile.type,
357      lastModified: sourceFile.lastModified || Date.now()
358    });
359    let compressId = prjutils.uploadFileIndex++;
360    $file.attr('data-compressId', compressId);
361    prjutils.uploadFiles[compressId] = compressFile;
362    return compressFile;
363  },
364  // ファイルを縮小する
365  // file File ファイルオブジェクト
366  compressImageFile: async function(file) {
367    let sourceFile = await prjutils.convertHeicToJpeg(file);
368    let compressBlob = await imageCompression(sourceFile, prjutils.CompressOption);
369    let compressFile = new File([compressBlob], sourceFile.name, {
370      type: compressBlob.type || sourceFile.type,
371      lastModified: sourceFile.lastModified || Date.now()
372    });
373    let compressId = prjutils.uploadFileIndex++;
374    prjutils.uploadFiles[compressId] = compressFile;
375    return compressFile;
376  },
377
378  // postデータを縮小ファイルに置き換える
379  // 未圧縮(プレビュー無し)の場合、非圧縮のままとする
380  switchToCompress: function($form, postMap) {
381    let testMap = [];
382    for(var pair of postMap.entries()) {
383      let key = pair[0];
384      let value = pair[1];
385      if (value instanceof File) {
386        let $file = $form.find('input[name="' + key + '"]');
387        let compressId = $file.attr('data-compressId');
388        let compressFile = prjutils.uploadFiles[compressId];
389        if(compressFile) {
390          postMap.set(key, compressFile);
391          testMap[key] = compressFile;
392        }
393      }
394    }
395  },
396  // postデータを縮小ファイルに置き換える
397  // 未圧縮(プレビュー無し)の場合ここで圧縮する
398  switchToCompressAsync: async function($form, postMap) {
399    prjutils.loadingImg();
400    for(var pair of postMap.entries()) {
401      let key = pair[0];
402      let value = pair[1];
403      if (value instanceof File) {
404        let $file = $form.find('input[name="' + key + '"]');
405        let compressId = $file.attr('data-compressId');
406        let compressFile = null;
407        let isMovie = false;
408        if($file.length > 0) {
409          let fileName = $file.prop('files')[0].name;
410          isMovie = prjutils.isMovieFilename(fileName);
411        }
412        if(!isMovie) {
413          if(!prjutils.uploadFiles[compressId]) {
414            // 未圧縮ならここで圧縮
415            compressId = prjutils.uploadFileIndex;
416            if($file.get(0)) {
417              await prjutils.compressImage($file.get(0));
418            } else {
419              await prjutils.compressImageFile(value);
420            }
421          }
422          compressFile = prjutils.uploadFiles[compressId];
423          if(compressFile) {
424            postMap.set(key, compressFile);
425          }
426        }
427      }
428    }
429    prjutils.loadingImgDel();
430  },
431
432  putMsg : function(msg, confirmFunc) {
433    alert_msg_html(msg, confirmFunc);
434  },
435  putMsgInfo : function(msg, confirmFunc) {
436    success_msg(msg, confirmFunc);
437  },
438  // SNSログインボタンの遷移先パラメータ(pag)引き継ぎの初期化
439  initSnsLoginRedirect : function(defaultPag) {
440    let urlParams = new URLSearchParams(window.location.search);
441    let pag = urlParams.get('pag') || defaultPag;
442    if (pag) {
443      $('a[href*="loginSocial="]').each(function() {
444        let href = $(this).attr('href');
445        if (href) {
446          let url = new URL(href, window.location.origin);
447          let redirect = url.searchParams.get('redirect');
448          if (redirect) {
449            let redirectUrl = new URL(redirect, window.location.origin);
450            redirectUrl.searchParams.set('pag', pag);
451            url.searchParams.set('redirect', redirectUrl.toString());
452            $(this).attr('href', url.toString());
453          }
454        }
455      });
456    }
457  },
458
459  // フォーム内項目を編集不可モードにする
460  setFormDisabled: function($itemInForm) {
461    var $form = $itemInForm;
462    if(!$form.is('form')) {
463      $form = $itemInForm.closest('form');
464    }
465    $form.find('input,select,textarea').filter(':enabled').each(function(){
466      var $item = $(this);
467      if($item.closest('.controller').length > 0){
468        return true; // continue;
469      }
470      $item.addClass('default_enabled');
471      $item.prop('disabled', true);
472    });
473  },
474
475  // フォーム内項目を編集可能モードにする
476  // 対象は本来編集できる項目(.default_enabled)のみ
477  setFormEnabled: function($itemInForm) {
478    var $form = $itemInForm;
479    if(!$form.is('form')) {
480      $form = $itemInForm.closest('form');
481    }
482    $form.find('input,select,textarea').filter('.default_enabled').each(function(){
483      var $item = $(this);
484      $item.removeClass('default_enabled');
485      $item.prop('disabled', false);
486    });
487  },
488
489  /**
490   * ドラッグでスクロール可能にする。
491   * @param {jQuery} $target スクロール対象範囲
492   */
493  setDragScroll: function($target) {
494    let $document = $(document);
495    // スクロール開始
496    $target.mousedown(function (event){
497        $target.data('down', true);
498        $target.data('x', event.clientX);
499        $target.data('y', event.clientY);
500        $target.data('scrollLeft', this.scrollLeft);
501        $target.data('scrollTop', this.scrollTop);
502        return false;
503    });
504    $target.on('touchstart', function (event){
505      $target.data('down', true);
506      $target.data('x', event.changedTouches[0].clientX);
507      $target.data('y', event.changedTouches[0].clientY);
508      $target.data('scrollLeft', this.scrollLeft);
509      $target.data('scrollTop', this.scrollTop);
510      return;
511    });
512
513    // スクロール中
514    $document.mousemove(function(event){
515        if($target.data('down') == true){
516          let xMove = $target.data('x') - event.clientX;
517          let yMove = $target.data('y') - event.clientY;
518          $target.scrollLeft($target.data('scrollLeft') + xMove);
519          $target.scrollTop($target.data('scrollTop') + yMove);
520
521          if(Math.abs(xMove) > 5 ||  Math.abs(yMove) > 5){
522            $target.data('moved', true);
523          }
524          return false;
525        }
526    });
527    $document.on('touchmove', function(event){
528      if($target.data('down') == true){
529        let xMove = $target.data('x') - event.changedTouches[0].clientX;
530        let yMove = $target.data('y') - event.changedTouches[0].clientY;
531        $target.scrollLeft($target.data('scrollLeft') + xMove);
532        $target.scrollTop($target.data('scrollTop') + yMove);
533
534        if(Math.abs(xMove) > 5 ||  Math.abs(yMove) > 5){
535          $target.data('moved', true);
536        }
537      }
538    });
539    // スクロール終了
540    $document.mouseup(function (event){
541        if($target.data('down') == true){
542            $target.data('down', false);
543            setTimeout(function(){
544                $target.data('moved', false);
545            }, 0);
546            return false;
547        }
548        return false;
549    });
550    $document.on('touchend', function (event){
551      if($target.data('down') == true){
552          $target.data('down', false);
553          setTimeout(function(){
554              $target.data('moved', false);
555          }, 0);
556          return;
557      }
558      return;
559    });
560    // スクロールした場合クリックを無効化
561    $target.find('[onclick!=""]').on('click', function(event){
562        if($target.data('moved') == true){
563            return false;
564        }
565    });
566  },
567
568  /**
569   * 縦横比の指定で高さ調整イベントを設置する。
570   */
571  setAspectHeightEvent: function($items, widthRatio, heightRatio) {
572    $(window).resize(function(){
573      prjutils.setAspectHeightSize($items, widthRatio, heightRatio);
574    });
575    prjutils.setAspectHeightSize($items, widthRatio, heightRatio);
576  },
577
578  /**
579   * 縦横比の指定で高さを1回設定する。
580   */
581  setAspectHeightSize: function($items, widthRatio, heightRatio) {
582    $items.each(function(idx, element){
583      let $item = $(element);
584      let height = $item.innerWidth() * heightRatio / widthRatio;
585      $item.innerHeight(height);
586    });
587  },
588
589  /**
590   * 現在日付をyyyy-mm-ddで取得する。
591   */
592  getToday: function(){
593    let now = new Date();
594    let year = now.getFullYear();
595    let month = String(now.getMonth() + 1).padStart(2, '0');
596    let day = String(now.getDate()).padStart(2, '0');    
597    return `${year}-${month}-${day}`;
598  },
599
600  /**
601   * 現在日付をyyyy-mmで取得する。
602   */
603  getThisMonth: function(){
604    let now = new Date();
605    let year = now.getFullYear();
606    let month = String(now.getMonth() + 1).padStart(2, '0');
607    return `${year}-${month}`;
608  },
609
610  /**
611   * 月末日をyyyy-mm-ddで取得する。
612   * @param {Date|String} date 
613   */
614  getLastDayOfMonth: function(date) {
615    let dt = new Date(date);
616    dt.setDate(1);
617    dt.setMonth(dt.getMonth() + 1);
618    dt.setDate(0);
619    return prjutils.toYMD(dt);
620  },
621
622  /**
623   * 週の先頭日付(日曜)を取得する
624   * @param {Date} date 
625   * @returns 週の先頭日付
626   */
627  getStartOfWeek: function(date) {
628    let startWeek = new Date(date);
629    startWeek.setDate(date.getDate() - date.getDay());
630    return startWeek;
631  },
632
633  /**
634   * 週の先頭日付(月曜)を取得する
635   * @param {Date} date 
636   * @returns 週の先頭月曜の日付
637   */
638  getMondayOfWeek: function(date) {
639    let startWeek = new Date(date);
640    let day = date.getDay();
641    // 月曜始まりにIndex補正
642    let diff = (day === 0) ? 6 : day - 1;
643    startWeek.setDate(date.getDate() - diff);
644    return startWeek;
645  },
646
647  /**
648   * 2つの日付の差分日数を取得する
649   * @param {Date} dateFrom 
650   * @param {Date} dateTo 
651   * @returns 差分日数
652   */
653  getBetweenDays: function(dateFrom, dateTo) {
654    let oneDayMs = 24 * 60 * 60 * 1000; // 1日のミリ秒
655    let timeDiffMs = Math.abs(dateTo - dateFrom);
656    let days = Math.floor(timeDiffMs / oneDayMs); // 日数を計算
657    return days;
658  },
659
660  /**
661   * 月数を加算する
662   * @param {Date} date 
663   * @param {number} months 加算月数
664   */
665  addMonth: function(date, months){
666    let newDate = new Date(date);
667    newDate.setMonth(newDate.getMonth() + months);
668    newDate.setDate(newDate.getDate() - 1);
669    return newDate;
670  },
671
672  /**
673   * 日数を加算する
674   * @param {Date} date 
675   * @param {number} days 加算日数
676   */
677  addDays: function(date, days) {
678    let newDate = new Date(date);
679    newDate.setDate(newDate.getDate() + days);
680    return newDate;
681  },
682
683  /**
684   * 時間(hour)を加算する
685   * @param {Date} date 
686   * @param {number} hours 加算時間
687   */
688  addHours: function(date, hours){
689    let newDate = new Date(date);
690    newDate.setHours(newDate.getHours() + hours);
691    return newDate;
692  },
693
694  /**
695   * 指定日付をyyyy-mmで取得する。
696   */
697  toYM: function(date) {
698    if(!date){
699      return '';
700    }
701    let year = date.getFullYear();
702    let month = String(date.getMonth() + 1).padStart(2, '0'); 
703    return `${year}-${month}`;
704  },
705
706  /**
707   * 指定日付をyyyy-mm-ddで取得する。
708   */
709  toYMD: function(date) {
710    if(!date){
711      return '';
712    }
713    let year = date.getFullYear();
714    let month = String(date.getMonth() + 1).padStart(2, '0');
715    let day = String(date.getDate()).padStart(2, '0');    
716    return `${year}-${month}-${day}`;
717  },
718
719  /**
720   * 指定日付をyyyymmddで取得する。
721   */
722  toYYYYMMDD(date) {
723    let str = prjutils.toYMD(date);
724    str = str.replaceAll('-', '');
725    return str;
726  },
727
728  /**
729   * yyyymmddをDateに変換する
730   * @param {string} yyyymmdd 
731   */
732  toDateFromYYYYMMDD(yyyymmdd) {
733    let yyyy = yyyymmdd.substring(0, 4);
734    let mm = yyyymmdd.substring(4, 6);
735    let dd = yyyymmdd.substring(6, 8);
736    let newDate = new Date(yyyy + '-' + mm + '-' + dd);
737    return newDate;
738  },
739
740  /**
741   * 日付入力用フォーマットで取得する
742   * @param {Date} date 変換元日付
743   * @return {String} 変換後日付文字列
744   */
745  getInputDate: function(date){
746    var local;
747    if(!date){
748        local = new Date();
749    } else {
750        local = new Date(date);
751    }
752    if(isNaN(local)) {
753      return '';
754    }
755    local.setMinutes(local.getMinutes() - local.getTimezoneOffset());
756    return local.toJSON().slice(0,10);
757  },
758
759  /**
760   * 日付文字列を入力用(yyyy-mm-dd)に変換する。
761   * @param {String} textdate 日付文字列
762   */
763  formatInputDate: function(textdate) {
764      if(!textdate){
765        return textdate;
766      }
767      textdate = textdate.replace(/[年月日./]/g, '-');
768      return textdate;
769  },
770
771  /**
772   * 日付文字列を表示用(yyyy/mm/dd)に変換する。
773   * @param {String} textdate 日付文字列
774   */
775  formatDisplayDate: function(textdate) {
776    if(!textdate){
777      return textdate;
778    }
779    textdate = textdate.replace(/[年月日.-]/g, '/');
780    return textdate;
781  },
782  
783  /**
784   * 日付文字列をyyyy/m/dに変換する。
785   * @param {String} textdate 
786   */
787  formatYmd(textdate) {
788    let local;
789    if(!textdate){
790        return "";
791    } else {
792        local = new Date(textdate);
793    }
794    let year = local.getFullYear();
795    let month = String(local.getMonth() + 1);
796    let day = String(local.getDate());    
797    return `${year}/${month}/${day}`;
798  },
799
800  /**
801   * 日付文字列を時間x:xxに変換する。
802   * @param {String} textdate 
803   */
804  formatHmm(textdate) {
805    let local;
806    if(!textdate){
807        return "";
808    } else {
809        local = new Date(textdate);
810    }
811    let hour = String(local.getHours());
812    let minute = String(local.getMinutes()).padStart(2, '0');
813    return `${hour}:${minute}`;
814  },
815
816  /**
817   * 日付文字列をyyyy/m/d x:xxに変換する。
818   * @param {String} textdate 
819   */
820  formatYmdTime(textdate) {
821    let local;
822    if(!textdate){
823        return "";
824    } else {
825        local = new Date(textdate);
826    }
827    let year = local.getFullYear();
828    let month = String(local.getMonth() + 1);
829    let day = String(local.getDate());
830    let hour = String(local.getHours());
831    let minute = String(local.getMinutes()).padStart(2, '0');
832    return `${year}/${month}/${day} ${hour}:${minute}`;
833  },
834  
835  /**
836   * 日付をy年m月に変換する
837   * @param {String|Date} textdate 
838   */
839  formatYearMonth(textdate) {
840    let local;
841    if(!textdate){
842        return "";
843    } else {
844        local = new Date(textdate);
845    }
846    let year = String(local.getYear() + 1900);
847    let month = String(local.getMonth() + 1);
848    return `${year}年${month}月`;
849  },
850
851  /**
852   * 日付をm月d日に変換する
853   * @param {String|Date} textdate 
854   */
855  formatMonthDate(textdate) {
856    let local;
857    if(!textdate){
858        return "";
859    } else {
860        local = new Date(textdate);
861    }
862    let month = String(local.getMonth() + 1);
863    let day = String(local.getDate());
864    return `${month}月${day}日`;
865  },
866
867  /**
868   * 時分秒(hh:mm:ss or hh:mm)を時分(h:mm)に変換する
869   * @param {string} hhmmss 時分秒
870   */
871  formatTimeToHmm(hhmmss) {
872    if(!hhmmss) {
873      return '';
874    }
875    let arrTime = hhmmss.split(':');
876    let res = parseInt(arrTime[0]) + ':';
877    res += arrTime[1];
878    return res;
879  },
880
881  /**
882   * 時分秒(hh:mm:ss or hh:mm)を時(h)に変換する
883   * @param {string} hhmmss 時分秒
884   */
885  formatH(hhmmss) {
886    if(!hhmmss) {
887      return '';
888    }
889    let arrTime = hhmmss.split(':');
890    let res = parseInt(arrTime[0]);
891    return res;
892  },
893
894  /**
895   * 時分秒(hh:mm:ss or hh:mm)を分(m)に変換する
896   * @param {string} hhmmss 時分秒
897   */
898  formatM(hhmmss) {
899    if(!hhmmss) {
900      return '';
901    }
902    let arrTime = hhmmss.split(':');
903    let res = parseInt(arrTime[1]);
904    return res;
905  },
906
907  /**
908   * 表示/非表示を設定する
909   * @param {jQuery} $item 制御対象エレメント 
910   * @param {bool} visible true=表示 
911   */
912  setVisible: function($item, visible){
913    if(visible){
914      $item.show();
915    } else {
916      $item.hide();
917    }
918  },
919
920  /**
921   * 無効属性を設定する
922   * @param {jQuery} $item 制御対象エレメント 
923   * @param {bool} disabled true=無効 
924   */
925  setDisabled: function($item, disabled){
926    $item.prop("disabled", disabled);
927  },
928
929  /**
930   * 入力項目に値を設定する
931   * @param {jquery} $item 入力項目 
932   * @param {object} value 設定する値
933   * @param {bool} isCheckOnOnly true=チェックボックスをONのみ設定、OFFに戻さない。
934   */
935  setValue: function($item, value, isCheckOnOnly = false) {
936    if($item.is('input[type="checkbox"]')) {
937      let checkValue = $item.val();
938      let isChecked = checkValue === value || value === true || value == 1 || value == 'y' || value == 't' || value == 'on';
939      if(isCheckOnOnly) {
940        if(isChecked) {
941          $item.prop('checked', isChecked);  
942        }
943      } else {
944        $item.prop('checked', isChecked);
945      }
946    } else if($item.is('input[type="radio"]')) {
947      let checkValue = $item.val();
948      let isChecked = checkValue === value;
949      if(isCheckOnOnly) {
950        if(isChecked) {
951          $item.prop('checked', isChecked);  
952        }
953      } else {
954        $item.prop('checked', isChecked);
955      }
956    } else {
957      $item.val(value);
958    }
959  },
960
961  /**
962   * 入力項目の値を取得する
963   * ※checkboxはONなら1を返す
964   * @param {jquery} $item 入力項目
965   */
966  getValue: function($item){
967    let value = '';
968    if($item.is('input[type="checkbox"]')) {
969      if($item.is(':checked')) {
970        value = 1;
971      }
972    } else if($item.is('select')) {
973      value = $item.find('option:selected').val();
974    } else {
975      value = $item.val();
976    }
977    return value;
978  },
979
980  /**
981   * 入力項目の値を取得する
982   * ※checkboxはvalue値をそのまま返す
983   * @param {jquery} $item 入力項目
984   */
985  getValueReal: function($item) {
986    let value = '';
987    if($item.is('input[type="checkbox"]')) {
988      if($item.is(':checked')) {
989        value = $item.val();
990      }
991    } else {
992      value = prjutils.getValue($item);
993    }
994    return value;
995  },
996
997  /**
998   * 指定範囲内のname属性をmapに収集する
999   * @param {jQuery} $area 収集範囲
1000   * @param {array} inputのnameをキーとするmap
1001   */
1002  getFormValues: function($area) {
1003    let $arr = {};
1004    $area.find('[name]').each(function(){
1005      let $item = $(this);
1006      let name = $item.attr('name');
1007      $arr[name] = $item.val();
1008    });
1009    return $arr;
1010  },
1011
1012  /**
1013   * 指定範囲内の number_inputクラス をカンマ区切り数値入力にする。
1014   * @param {jQuery} $area 収集範囲 
1015   */
1016  setNumberInput: function($area){
1017    let $targets = $area;
1018    if (!$targets.is('input.number_input')) {
1019      $targets = $area.find('input.number_input');
1020    }
1021    $targets.each(function(){
1022      let $input = $(this);
1023      $input.off('focus.setNumberInput').on('focus.setNumberInput', function () {
1024        let num = $input.val().replace(/\D/g, '');
1025        $input.val(num);
1026      }).off('blur.setNumberInput').on('blur.setNumberInput', function () {
1027        let text = $input.val();
1028        text = toHarfNumAlphabet(text);
1029        let num = text.replace(/\D/g, '');
1030        if(num != "") {
1031          let min = $input.data('min');
1032          if(min && Number(num) < Number(min)) {
1033            num = String(min);
1034          }
1035          let max = $input.data('max');
1036          if(max && Number(max) < Number(num)) {
1037            num = String(max);
1038          }
1039          num = num.replace(/(\d)(?=(\d\d\d)+$)/g, '$1,');
1040        }
1041
1042        $input.val(num);
1043      });
1044    });
1045    $targets.trigger('blur');
1046  },
1047
1048  /**
1049   * 入力項目の現在の入力文字数を表示する
1050   * @param {jQuery} $textCounterDiv 入力項目の外枠 
1051   */
1052  setTextCounter: function($textCounterDiv) {
1053    if(!$textCounterDiv.is('.text_counter')){
1054        return;
1055    }
1056    let $input = $textCounterDiv.find('input,textarea');
1057    let $textNum = $textCounterDiv.find('.text_num');
1058    if($textNum.length == 0){
1059        $textNum = $('<div class="text_num"></div>');
1060        $textCounterDiv.append($textNum);
1061    }
1062    let length = $input.val().length;
1063    let max = $input.attr('maxlength');
1064    let text = length;
1065    
1066    if (max) {
1067        text = text + '/' + max + '文字';
1068    }
1069    $textNum.text(text);
1070  },
1071  /**
1072   * 入力文字数表示を現在値で更新する
1073   * @param {jQuery} $area 更新範囲
1074   */
1075  resetTextCounter: function($area){
1076    $area.find('.text_counter').each(function(){
1077        prjutils.setTextCounter($(this));
1078    });
1079  },
1080  /**
1081   * 上下移動ボタンの表示を正常化する。
1082   * @param {jQuery} $tbody 上下移動ボタンのあるtbodyタグ
1083   * @param {number} firstIndex 移動可能な先頭行のインデックス
1084   */
1085  refreshTableOrder: function($tbody, firstIndex){
1086    let $trList = $tbody.children('tr:not(.sample)');
1087    for(let i = 0; i < $trList.length; i++){
1088      let $tr = $($trList[i]);
1089      let isLocked = i < firstIndex;
1090      let isLast = i == ($trList.length - 1);
1091
1092      let bUp = true;
1093      let bDown = true;
1094      let bDelete = true;
1095      if(isLocked){
1096        bUp = false;
1097        bDown = false;
1098        bDelete = false;
1099      }
1100      if(i == firstIndex){
1101        bUp = false;
1102      }
1103      if(isLast){
1104        bDown = false;
1105      }
1106
1107      prjutils.setVisible($tr.find('.up'), bUp);
1108      prjutils.setVisible($tr.find('.down'), bDown);
1109      prjutils.setVisible($tr.find('.delete'), bDelete);
1110    }
1111  },
1112
1113  // エリア内すべてのpreタグ内のURLをリンク化する
1114  setTextUrlLinkAll: function($area) {
1115    $area.find('pre,div.top_experience .text_experience').each(function(){
1116      let $item = $(this);
1117      prjutils.setUrlLink($item);
1118      prjutils.setMailLink($item);
1119    });
1120  },
1121  // 指定範囲のテキストをリンク化する
1122  setTextUrlLinkDirect: function($areaList) {
1123    $areaList.each(function(){
1124      let $item = $(this);
1125      prjutils.setUrlLink($item);
1126      prjutils.setMailLink($item);
1127    });
1128  },
1129
1130  /**
1131   * 文章中のURLをリンク化する。
1132   * @param {jQuery} $box 適用する範囲preやdivタグ
1133   */
1134  setUrlLink: function($box) {
1135    //var exp = /((?<!href="|href='|src="|src=')(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
1136    var exp = /(?<!="|=')((https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
1137    $box.html($box.html().replace(exp, '<a href="$1" rel="noopener" target="_blank">$1</a>'));
1138  },
1139
1140  /**
1141   * 文章中のメールアドレスをリンク化する。
1142   * @param {jQuery} $box 適用する範囲preやdivタグ
1143   */
1144  setMailLink: function($box) {
1145    //var exp = /([\w\-._]+@[\w\-._]+\.[A-Za-z]+)/ig;
1146    var exp = /([\w\-._]+@[\w\-._]+\.[A-Za-z]+)(?![@\.A-Za-z]*")/ig;
1147    $box.html($box.html().replace(exp, '<a href="mailto:$1" target="_blank">$1</a>'));
1148  },
1149
1150  /**
1151   * 指定エリア内のテキストエリアの高さを自動調整とする
1152   * @param {jQuery} 捜索範囲
1153   * @param {int}
1153 minHeight テキストエリアの最小の高さ
1154   */
1155  setTextareaHeightAuto: function($area, minHeight) {
1156    if(!minHeight) {
1157      minHeight = 60;
1158    }
1159    $(document).ready(function() {
1160      $area.on("input", "textarea", function(){
1161        let textarea = this;
1162        // let scrollHeightOld = null;
1163        let scrollHeight = null; //textarea.clientHeight;
1164        let clientHeight;
1165        let $body = $('body');
1166        let posY;
1167        if ('scrollingElement' in document) {
1168          posY = document.scrollingElement.scrollTop;
1169        } else {
1170          posY = document.body.scrollTop;
1171        }
1172        textarea.style.overflowY = 'hidden';
1173        for(i = 0; i < 100; i++) { // ループが続く場合100回で離脱
1174          scrollHeight = textarea.scrollHeight;
1175          clientHeight = textarea.clientHeight;
1176          // if(scrollHeight != null && scrollHeightOld != null && scrollHeightOld <= scrollHeight) {
1177          //   // 縦を縮めて縦スクロールが発生したためコンテンツが縦に伸びたので前回の高さを採用
1178          //   scrollHeight = scrollHeightOld;
1179          //   break;
1180          // }
1181          if(clientHeight < scrollHeight || clientHeight < minHeight) {
1182            break;
1183          }
1184          // scrollHeightOld = scrollHeight;
1185          textarea.style.height = (clientHeight - 30) + "px";
1186        }
1187        let height = Math.max(scrollHeight + 5, minHeight);
1188        textarea.style.height = height + "px";
1189        textarea.style.overflowY = 'auto';
1190        // window.scrollTo(0 , posY);
1191      });
1192      $area.find('textarea').each(function(){
1193        let $textarea = $(this);
1194        $textarea.trigger('input');
1195      });
1196      prjutils.resetTextareaHeightAuto($area);
1197    });
1198  },
1199
1200  /**
1201   * 指定エリア内のテキストエリアの高さを再調整とする
1202   * @param {jQuery} 捜索範囲
1203   */
1204  resetTextareaHeightAuto: function($area) {
1205    if ($area.is('textarea')) {
1206      $area.trigger('input');
1207    } else {
1208      $area.find('textarea').each(function(){
1209        let $textarea = $(this);
1210        $textarea.trigger('input');
1211      });
1212    }
1213  },
1214
1215  /**
1216   * 月選択トグルを設置する
1217   * @param {jQuery} $area .selectedMonthを一つだけ含む範囲
1218   */
1219  setMonthSelection($area, $option = {}) {
1220    let $hidden = $area.find('input[type="hidden"].selectedMonth');
1221    let funcPrev = null;
1222    if ($option['after_prev']) {
1223      funcPrev = $option['after_prev'];
1224    }
1225    let funcNext = null;
1226    if ($option['after_next']) {
1227      funcNext = $option['after_next'];
1228    }
1229    let funcThis = null;
1230    if ($option['after_this']) {
1231      funcThis = $option['after_this'];
1232    }
1233    $area.find('.prevMonth').off('click.setMonthSelection');
1234    $area.find('.prevMonth').on('click.setMonthSelection',function(){
1235      prjutils.prevMonthSelection($area);
1236      if (funcPrev) {
1237        funcPrev();
1238      }
1239    });
1240    $area.find('.nextMonth').off('click.setMonthSelection');
1241    $area.find('.nextMonth').on('click.setMonthSelection',function(){
1242      prjutils.nextMonthSelection($area);
1243      if(funcNext) {
1244        funcNext();
1245      }
1246    });
1247    $area.find('.thisMonth').off('click.setMonthSelection');
1248    $area.find('.thisMonth').on('click.setMonthSelection',function(){
1249      let yyyyMm = prjutils.getThisMonth();
1250      $hidden.val(yyyyMm);
1251      prjutils.loadMonthSelection($area);
1252      if(funcThis) {
1253        funcThis();
1254      }
1255    });
1256    // 現在月を初期設定
1257    let yyyyMm = prjutils.getThisMonth();
1258    $hidden.val(yyyyMm);
1259    prjutils.loadMonthSelection($area);
1260  },
1261  /**
1262   * 月選択トグルを内部値に沿って表示する
1263   * @param {jQuery} $area 
1264   */
1265  loadMonthSelection($area) {
1266    let $hidden = $area.find('input[type="hidden"].selectedMonth');
1267    let $label =  $area.find('.monthName');
1268    let selectedMonth = new Date($hidden.val() + '-01');
1269    let yearMonth = selectedMonth.getFullYear() + "年 " + String(selectedMonth.getMonth() + 1) + "月";
1270    $label.text(yearMonth);
1271  },
1272  // 前月へ
1273  prevMonthSelection: function($area) {
1274    let $hidden = $area.find('input[type="hidden"].selectedMonth');
1275    let selectedMonth = new Date($hidden.val() + '-01');
1276    selectedMonth.setMonth(selectedMonth.getMonth() - 1);
1277    $hidden.val(prjutils.toYM(selectedMonth));
1278    prjutils.loadMonthSelection($area);
1279  },
1280  // 翌月へ
1281  nextMonthSelection: function($area) {
1282    let $hidden = $area.find('input[type="hidden"].selectedMonth');
1283    let selectedMonth = new Date($hidden.val() + '-01');
1284    selectedMonth.setMonth(selectedMonth.getMonth() + 1);
1285    $hidden.val(prjutils.toYM(selectedMonth));
1286    prjutils.loadMonthSelection($area);
1287  },
1288
1289  /**
1290   * 週選択トグルを設置する
1291   * @param {jQuery} $area .selectedWeekを一つだけ含む範囲
1292   */
1293  setWeekSelection($area, $option = {}) {
1294    let $hidden = $area.find('input[type="hidden"].selectedWeek');
1295    let funcPrev = null;
1296    if ($option['after_prev']) {
1297      funcPrev = $option['after_prev'];
1298    }
1299    let funcNext = null;
1300    if ($option['after_next']) {
1301      funcNext = $option['after_next'];
1302    }
1303    let funcThis = null;
1304    if ($option['after_this']) {
1305      funcThis = $option['after_this'];
1306    }
1307    
1308    $area.find('.prevWeek').on('click',function(){
1309      prjutils.prevWeekSelection($area);
1310      if (funcPrev) {
1311        funcPrev();
1312      }
1313    });
1314    $area.find('.nextWeek').on('click',function(){
1315      prjutils.nextWeekSelection($area);
1316      if(funcNext) {
1317        funcNext();
1318      }
1319    });
1320    $area.find('.thisWeek').on('click',function(){
1321      let now = new Date();
1322      let yyyyMmDd = prjutils.toYMD(prjutils.getMondayOfWeek(now));
1323      $hidden.val(yyyyMmDd);
1324      prjutils.loadWeekSelection($area);
1325      if(funcThis) {
1326        funcThis();
1327      }
1328    });
1329    // 現在週を初期設定
1330    let now = new Date();
1331    let todayYyyyMdDd = prjutils.toYMD(prjutils.getMondayOfWeek(now));
1332    let urlParams = new URLSearchParams(window.location.search);
1333    let initWeek = urlParams.get('week');
1334    if(initWeek && /^\d{4}-\d{2}-\d{2}$/.test(initWeek)){
1335      $hidden.val(initWeek);
1336    } else {
1337      $hidden.val(todayYyyyMdDd);
1338    }
1339    prjutils.loadWeekSelection($area);
1340  },
1341  /**
1342   * 週選択トグルを内部値に沿って表示する
1343   * @param {jQuery} $area 
1344   */
1345  loadWeekSelection($area) {
1346    let $hidden = $area.find('input[type="hidden"].selectedWeek');
1347    let $label =  $area.find('.weekName');
1348    let selectedDate = new Date($hidden.val());
1349    let yearMonthDate = selectedDate.getFullYear() + "年 " + String(selectedDate.getMonth() + 1) + "月 " + selectedDate.getDate() + "日~";
1350    $label.text(yearMonthDate);
1351
1352    // 今週か判定
1353    let now = new Date();
1354    let selectedYMD = prjutils.toYMD(selectedDate);
1355    let todayYMD = prjutils.toYMD(prjutils.getMondayOfWeek(now));
1356    if(selectedYMD == todayYMD) {
1357      $area.find('.thisWeek').addClass('visibility_hidden');
1358    } else {
1359      $area.find('.thisWeek').removeClass('visibility_hidden');
1360    }
1361  },
1362  // 前週へ
1363  prevWeekSelection: function($area) {
1364    let $hidden = $area.find('input[type="hidden"].selectedWeek');
1365    let selectedDate = new Date($hidden.val());
1366    selectedDate.setDate(selectedDate.getDate() - 7);
1367    $hidden.val(prjutils.toYMD(selectedDate));
1368    prjutils.loadWeekSelection($area);
1369  },
1370  // 翌週へ
1371  nextWeekSelection: function($area) {
1372    let $hidden = $area.find('input[type="hidden"].selectedWeek');
1373    let selectedDate = new Date($hidden.val());
1374    selectedDate.setDate(selectedDate.getDate() + 7);
1375    $hidden.val(prjutils.toYMD(selectedDate));
1376    prjutils.loadWeekSelection($area);
1377  },
1378
1379  /**
1380   * フォーム値の複数値をcsv形式に変換する
1381   * 検索に対応するため前後にも,を付ける
1382   * @param {FormData} formData 
1383   * @param {string} key 
1384   */
1385  setArrayToCsv: function(formData, key) {
1386    var values = formData.getAll(key);
1387    if (values.length > 0) {
1388      formData.delete(key);
1389      formData.set(key, ',' + values.join(',') + ',');
1390    }
1391  },
1392
1393  // map値をGETパラメータ向け文字列に変換する
1394  toGetParam: function(map) {
1395    let arr = [];
1396    let keys = Object.keys(map);
1397    for(let key of keys) {
1398      arr.push(key + '=' + map[key]);
1399    }
1400    return arr.join('&');
1401  },
1402  sendRequest: function(backUrl) {
1403    send_confirm_msg(send_caution, function() {
1404        var $form = $('#formDialogRequest');
1405        var postParam = new FormData($form.get(0));
1406        postParam.append('action', 'resuestClubRes');
1407        $.ajax({
1408            type: 'POST',
1409            url: binds['ajaxurl'],
1410            data: postParam,
1411            contentType : false,
1412            processData : false,
1413            success: function(res) {
1414                if(res['error']) {
1415                    alert_msg(res['error'], null);
1416                    return false;
1417                }
1418                if(res['success'] === true) {
1419                    success_msg(success_send, function(){
1420                      if(backUrl){
1421                        document.location = backUrl;
1422                      } else {
1423                        $('#dialogRequest').hide();
1424                      }
1425                    });
1426                }
1427            }
1428        });
1429    });
1430  },
1431  // ダイアログモードで開く
1432  // displayMode 'new' 'edit'(デフォルト)
1433  showDialog: function($dialog, displayMode){
1434    if(!displayMode) {
1435      displayMode = 'edit';
1436    }
1437    $dialog.removeClass('new');
1438    $dialog.removeClass('edit');
1439    $dialog.addClass(displayMode);
1440    $dialog.show();
1441    // class指定ではスクロール位置がtopになってしまうのでcss指定する
1442    //$('html').addClass('dialog');
1443    $('html').css('overflow', 'hidden');
1444  },
1445  // ダイアログモードを解除して閉じる
1446  hideDialog: function($dialog){
1447    $dialog = $($dialog);
1448    if(!$dialog.is('.dialog')){
1449      $dialog = $dialog.closest('.dialog');
1450    }
1451    $dialog.fadeOut('fast');
1452    $('html').css('overflow', '');
1453  },
1454  // ダイアログを閉じる(hideDialogの別名)
1455  closeDialog: function(element) {
1456    prjutils.hideDialog(element);
1457  },
1458  // data-link, data-outerlinkを有効化する
1459  activateLink: function($area){
1460    $area.find('[data-link]').each(function(){
1461      let $this = $(this);
1462      let link = $this.attr('data-link');
1463      $this.on('click', function(){
1464        location.href = link;
1465      });
1466    });
1467    $area.find('[data-outerlink]').each(function(){
1468      let $this = $(this);
1469      let link = $this.attr('data-outerlink');
1470      $this.on('click', function(){
1471        window.open(link, "_blank");
1472      });
1473    });
1474  },
1475  // 空要素を削除する
1476  removeArrayEmpty: function(arr){
1477    let res = [];
1478    for(const value of arr) {
1479      if(value || value === 0 || value === false){
1480        res.push(value);
1481      }
1482    }
1483    return res;
1484  },
1485  // 数値をカンマ書式化する
1486  toNumberFormat: function(num){
1487    const formatter = new Intl.NumberFormat('ja-JP');
1488    return formatter.format(num);
1489  },
1490  // カンマ書式数字を数値化する
1491  toArrayNumber: function(arr){
1492    let res = [];
1493    for(let value of arr) {
1494      if(value){
1495        value = value.replaceAll(',', '');
1496      }
1497      res.push(parseInt(value, 10));
1498    }
1499    return res;
1500  },
1501  // 配列を昇順に並び変える
1502  sortArrayNumber: function(arr) {
1503    return arr.sort((left, right) => left - right);
1504  },
1505  // 指定位置が画面上端になるまでスクロールする
1506  scrollTo: function($box, speed = 250) {
1507    // $(document).scrollTop($box.offset().top);
1508    let position = $box.offset().top;
1509    $("html, body").animate({scrollTop:position}, speed, "swing");
1510  },
1511  // 指定位置へのスクロール、対象がなければ画面遷移する
1512  scrollOrForward: function($box, uri) {
1513    if($box.is(':visible')) {
1514      prjutils.scrollTo($box);
1515    } else if(uri) {
1516      location.href = uri;
1517    }
1518  },
1519  // スクロールヒント表示
1520  setScroolHint: function(selector) {
1521    new ScrollHint(selector, {
1522      scrollHintIconAppendClass: 'scroll-hint-icon-white', 
1523      remainingTime: 5000, // ms
1524      i18n: {
1525        scrollable: "スクロールできます"
1526      }
1527    });
1528  },
1529  // SP表示の場合にスクロールヒント表示
1530  setScroolHintForSP: function(selector) {
1531    if(window.innerWidth > 767) {
1532      // PC
1533      return;
1534    }
1535    prjutils.setScroolHint(selector);
1536  },
1537  // 入力項目をクリアする。
1538  clearFormValues: function($area) {
1539    $area.find('input:not(:button,:submit,:reset,:radio,:checkbox),textarea').val('');
1540    $area.find('select').prop('selectedIndex', 0);
1541    $area.find('checkbox, radiobox').prop('checked', false);
1542    $area.find('input[type=checkbox]').prop('checked', false);
1543  },
1544  // 指定IDの入力項目をクリアする。
1545  clearValues: function(idList) {
1546    for(let id of idList) {
1547      if(id){
1548        $('#' + id).val('');
1549      }
1550    }
1551  },
1552  // デフォルト値(data-default)に戻す
1553  resetDefault: function($area) {
1554    $area.find('[data-default]').each(function(){
1555      $this = $(this);
1556      let value = $this.data('default');
1557      prjutils.setValue($this, value);
1558    });
1559  },
1560  // 入力値をデフォルト値(data-default)に保持する
1561  setValueToDefault: function($area) {
1562    $area.find('[data-default]').each(function(){
1563      $this = $(this);
1564      let defaultVal = prjutils.getValue($this);
1565      $this.data('default', defaultVal);
1566    });
1567  },
1568  // 入力項目を無効Disabledにする。
1569  setDisabledForm: function($area) {
1570    $area.find('input,textarea,select,checkbox,radiobox').prop('disabled', true);
1571  },
1572  // 入力項目を有効Enabledにする。
1573  setEnabledForm: function($area) {
1574    $area.find('input,textarea,select,checkbox,radiobox').prop('disabled', false);
1575  },
1576  // 高機能エディタを有効化する
1577  setupHtmlEditor: function($textarea, custom = []) {
1578    // 改行維持
1579    let rawText = $textarea.val();
1580    let htmlText = rawText.replace(/\r?\n/g, '<br>');
1581    if(!htmlText.startsWith('<p>')) {
1582      htmlText = '<p>' + htmlText + '</p>';
1583    }
1584    $textarea.val(htmlText);
1585
1586    // 高機能エディタ有効化
1587    let defaultHeight = 300;
1588    if(custom['default_height']) {
1589      defaultHeight = custom['default_height'];
1590    }
1591    $textarea.summernote('destroy');
1592    $textarea.summernote({
1593      lang: 'ja-JP',
1594      height: defaultHeight,
1595      toolbar: [
1596        ['style', ['style']],
1597        ['font', ['bold', 'underline', 'superscript', 'subscript', 'clear']],
1598        ['fontname', ['fontname']],
1599        ['fontsize', ['fontsize']],
1600        ['color', ['color']],
1601        ['para', ['paragraph']],
1602        ['height', ['height']],
1603        ['table', ['table']],
1604        ['insert', ['link', 'picture', 'video', 'hr']],
1605        ['view', ['fullscreen', 'codeview']]
1606      ],
1607      callbacks: {
1608        onImageUpload: function(files) {
1609          for (let i = 0; i < files.length; i++) {
1610            prjutils.htmlEditorImagesUpload(files[i], $textarea);
1611          }
1612        },
1613        onPaste: function(e) {
1614          // 既存入力を保持
1615          let clipboardData = (e.originalEvent || e).clipboardData || window.clipboardData;
1616          let pastedData = clipboardData.getData('text/plain');
1617          // タグエスケープ
1618          pastedData = pastedData.replace(/</g, "&lt;").replace(/>/g, "&gt;");
1619          // 改行置換
1620          pastedData = pastedData.replace(/\r?\n/g, '<br>');
1621          // 貼り付け後の状態取得
1622          $textarea.summernote('pasteHTML', pastedData);
1623          let lastRange = $textarea.summernote('createRange');
1624          let newContent = $textarea.summernote('code');
1625          // 時差貼り付け
1626          setTimeout(function () {
1627            $textarea.summernote('code', newContent);
1628            lastRange.select();
1629            $textarea.summernote('focus');
1630          }, 10);
1631        }
1632      }
1633    });
1634  },
1635
1636  // 高機能エディタに適用する画像のアップロード処理
1637  htmlEditorImagesUpload: async function(postFile, $textarea) {
1638    let xhr = new XMLHttpRequest();
1639    xhr.withCredentials = false;
1640    xhr.open('POST', binds['ajaxurl']);
1641  
1642    // xhr.upload.onprogress = function (e) {
1643    //   progress(e.loaded / e.total * 100);
1644    // };
1645  
1646    xhr.onload = function() {
1647      var json;
1648  
1649      if (xhr.status === 403) {
1650        alert_msg('アップロードに失敗しました', { remove: true });
1651        return;
1652      }
1653  
1654      if (xhr.status < 200 || xhr.status >= 300) {
1655        alert_msg('アップロードに失敗しました');
1656        return;
1657      }
1658  
1659      json = JSON.parse(xhr.responseText);
1660  
1661      if (!json || typeof json.location != 'string' || json.success != true) {
1662        if (json.error) {
1663          alert_msg(json.error);
1664        } else {
1665          alert_msg('アップロードに失敗しました');
1666        }
1667        return;
1668      }
1669
1670      $textarea.summernote('insertImage', json.location);
1671    };
1672  
1673    xhr.onerror = function () {
1674      alert_msg('アップロードに失敗しました(' + xhr.status + ')');
1675    };
1676  
1677    let clubCd = getQueryParam('club_cd');
1678    if(!clubCd) {
1679      clubCd = '';
1680    }
1681    let formData = new FormData();
1682    try {
1683      let sourceFile = await prjutils.convertHeicToJpeg(postFile);
1684      let compressBlob = await imageCompression(sourceFile, prjutils.CompressOption);
1685      formData.append('file', compressBlob, sourceFile.name);
1686    } catch (error) {
1687      alert_msg(error && error.message ? error.message : '画像を変換できませんでした。');
1688      return;
1689    }
1690    formData.append('club_cd', clubCd);
1691    formData.append('action', 'tinyImagesPost');
1692  
1693    xhr.send(formData);
1694  },
1695
1696  /*
1697  // TinyMCEを有効化する
1698  // $textarea {jquery} 適用するテキストエリア
1699  setupTinyMCE: function($textarea, custom = null) {
1700    let options = {
1701      language: "ja",
1702      height: 300,
1703      // entity_encoding: "raw",
1704      // editor_selector: "tinyMCE",
1705      // relative_urls : false,
1706      // convert_urls : false,
1707      menubar: false,  // メニューバー無し
1708      statusbar: false,  // ステータスバー無し
1709      plugins: "textcolor lists image media link table paste autoresize",
1710      toolbar: [
1711        "undo redo | fontsizeselect | bold | backcolor forecolor | alignleft aligncenter alignright al
1711ignjustify | "
1712      + " bullist numlist outdent indent | image media link table | removeformat",
1713      ],
1714      toolbar_mode: 'wrap',
1715      images_upload_handler: prjutils.tinyImagesUploadHandler,
1716      convert_urls: false,
1717      paste_as_text: true, // pasteプラグイン 貼り付け時にタグスタイルを無視しテキストのみにする
1718      autoresize_bottom_margin: 30,
1719      setup: function (editor) {
1720        editor.on('init', function(){
1721          $('button[title="画像の挿入/編集"]').on('click', function(){
1722            setTimeout(function(){
1723              let $uploadLink = $('div[role="tab"]:contains("アップロード")');
1724              $uploadLink.trigger('focus');
1725              $uploadLink.trigger('click');
1726            }, 1);
1727          });
1728        });
1729      }
1730    };
1731    if (custom) {
1732      options = Object.assign(options, custom);
1733    }
1734    $textarea.tinymce(options);
1735  },
1736  */
1737  /*
1738  // TinyMCEに適用する画像のアップロード処理
1739  tinyImagesUploadHandler: async function(blobInfo, success, failure, progress) {
1740    let xhr = new XMLHttpRequest();
1741    xhr.withCredentials = false;
1742    xhr.open('POST', binds['ajaxurl']);
1743  
1744    xhr.upload.onprogress = function (e) {
1745      progress(e.loaded / e.total * 100);
1746    };
1747  
1748    xhr.onload = function() {
1749      var json;
1750  
1751      if (xhr.status === 403) {
1752        failure('アップロードに失敗しました', { remove: true });
1753        return;
1754      }
1755  
1756      if (xhr.status < 200 || xhr.status >= 300) {
1757        failure('アップロードに失敗しました');
1758        return;
1759      }
1760  
1761      json = JSON.parse(xhr.responseText);
1762  
1763      if (!json || typeof json.location != 'string' || json.success != true) {
1764        if (json.error) {
1765          failure(json.error);
1766        } else {
1767          failure('アップロードに失敗しました');
1768        }
1769        return;
1770      }
1771  
1772      success(json.location);
1773    };
1774  
1775    xhr.onerror = function () {
1776      failure('アップロードに失敗しました(' + xhr.status + ')');
1777    };
1778  
1779    let formData = new FormData();
1780    let compressBlob = await imageCompression(blobInfo.blob(), prjutils.CompressOption);
1781    formData.append('file', compressBlob, blobInfo.filename());    
1782    formData.append('action', 'tinyImagesPost');
1783  
1784    xhr.send(formData);
1785  },
1786  */
1787
1788  // HTML文字列をtextのみに変換する
1789  htmlToText: function(html) {
1790    let $div = $('<div></div>');
1791    html = html.replaceAll('</p><br>', '</p>');
1792    html = html.replaceAll('</p>', '</p>' + "\n");
1793    html = html.replaceAll('<br>', '<br>' + "\n");
1794    
1795    $div.append(html);
1796    let text = $div[0].innerText;
1797    return text;
1798  },
1799
1800  // tinyMCEで入力されたHTMLであるか判定する
1801  isTinyHtml: function(content) {
1802    let isHtml = false;
1803    if(content && content.startsWith('<')) {
1804      isHtml = true;
1805    } else if(content && content.includes('</')) {
1806      isHtml = true;
1807    }
1808    return isHtml;
1809  },
1810
1811  // テキスト入力モードを高機能HTMLモードに切り替える
1812  changeModeHtml: function(id) {
1813    let $textarea = $('#' + id);
1814    // ボタン
1815    $('#btnHtml_' + id).hide();
1816    $('#btnText_' + id).show();
1817    // 文字数表示
1818    $textarea.closest('.text_counter').find('.text_num').hide();
1819    // 内容
1820    $(function() {
1821      prjutils.setupHtmlEditor($textarea);
1822    });
1823  },
1824  // 高機能HTMLモードをテキスト入力モードに切り替える
1825  changeModeText: function(id) {
1826    confirm_msg_html("文字のみになります。<br>書式・画像・メディアは消えます。", function(){
1827      prjutils.setModeText(id);
1828    });
1829  },
1830  // 確認メッセージなしで、テキスト入力モードに切り替える
1831  setModeText: function(id) {
1832    let $textarea = $('#' + id);
1833    // ボタン
1834    $('#btnHtml_' + id).show();
1835    $('#btnText_' + id).hide();
1836    // 文字数表示
1837    $textarea.closest('.text_counter').find('.text_num').show();
1838    
1839    // 内容
1840    $textarea.summernote('destroy');
1841
1842    let text = prjutils.htmlToText($textarea.val());
1843    $textarea.val(text);
1844    $textarea.show();
1845    $textarea.css('visibility', '');
1846    // 上限cut
1847    if($textarea.attr('maxlength') > 0) {
1848      $textarea.val($textarea.val().substring(0, $textarea.attr('maxlength')));
1849    }
1850    $textarea.trigger('input'); // 文字数計算
1851  },
1852  // 生年月日3連プルダウンを設置する
1853  // class {birthday-year, birthday-month, birthday-day}
1854  setBirthdayPulldown: function($area, defaultAge = null){
1855    let now = new Date();
1856    let thisYear = now.getFullYear();
1857    let userBirthdayYear = $area.find('.birthday-year').get(0);
1858    let userBirthdayMonth = $area.find('.birthday-month').get(0);
1859    let userBirthdayDay = $area.find('.birthday-day').get(0);
1860    let $birthday = $area.find('.birthday');
1861    let hasHiddenLink = $birthday.length > 0;
1862    let dateDefault = '';
1863    if(defaultAge !== null) {
1864      dateDefault = new Date((thisYear - defaultAge) + '/6/15');
1865    }
1866    if($birthday.val()) {
1867      dateDefault = new Date($birthday.val());
1868    }
1869
1870    // 選択変更時イベント
1871    let changeTheDay = function() {
1872      let currentVal = $(userBirthdayDay).val();
1873      if(!userBirthdayDay) {
1874        return;
1875      }
1876      //日付の要素を削除
1877      userBirthdayDay.innerHTML = '';
1878      //選択された年月の最終日を計算
1879      let lastDayOfTheMonth = new Date(userBirthdayYear.value, userBirthdayMonth.value, 0).getDate();
1880      //選択された年月の日付を生成
1881      prjutils.createOptionForElements(userBirthdayDay, "");
1882      for(let i = 1; i <= lastDayOfTheMonth; i++) {
1883        prjutils.createOptionForElements(userBirthdayDay, i);
1884      }
1885      if(lastDayOfTheMonth < currentVal) {
1886        currentVal = lastDayOfTheMonth;
1887      }
1888      $(userBirthdayDay).val(currentVal);
1889
1890      if(hasHiddenLink) {
1891        if(userBirthdayYear.value && userBirthdayMonth.value && userBirthdayDay.value) {
1892          let date = new Date(userBirthdayYear.value + '/' + userBirthdayMonth.value + '/' + userBirthdayDay.value);
1893          let ymd = prjutils.toYMD(date);
1894          $birthday.val(ymd);
1895        } else {
1896          $birthday.val('');
1897        }
1898        $(userBirthdayYear).trigger('change');
1899      }
1900    }
1901    
1902    //年の生成
1903    if(userBirthdayYear) {
1904      prjutils.createOptionForElements(userBirthdayYear, "");
1905      for(let i = 1920; i <= thisYear; i++) {
1906        prjutils.createOptionForElements(userBirthdayYear, i);
1907      }
1908    }
1909
1910    //月の生成
1911    if(userBirthdayMonth) {
1912      prjutils.createOptionForElements(userBirthdayMonth, "");
1913      for(let i = 1; i <= 12; i++) {
1914        prjutils.createOptionForElements(userBirthdayMonth, i);
1915      }
1916    }
1917    
1918    //日の生成
1919    if(userBirthdayDay) {
1920      prjutils.createOptionForElements(userBirthdayDay, "");
1921      for(let i = 1; i <= 31; i++) {
1922        prjutils.createOptionForElements(userBirthdayDay, i);
1923      }
1924    }
1925
1926    if(dateDefault) {
1927      $(userBirthdayYear).val(dateDefault.getFullYear());
1928      $(userBirthdayMonth).val(dateDefault.getMonth() + 1);
1929      $(userBirthdayDay).val(dateDefault.getDate());
1930    }
1931    changeTheDay();
1932    
1933    if(userBirthdayYear) {
1934      userBirthdayYear.addEventListener('change', changeTheDay);
1935    }
1936    if(userBirthdayMonth) {
1937      userBirthdayMonth.addEventListener('change', changeTheDay);
1938    }
1939    if(userBirthdayDay) {
1940      userBirthdayDay.addEventListener('change', changeTheDay);
1941    }
1942  },
1943  // [JS] selectのoptionタグ(値とラベルは同じ)を生成する
1944  // @param {Element} pulldown プルダウン要素
1945  createOptionForElements(pulldown, val) {
1946    let option = document.createElement('option');
1947    option.text = val;
1948    option.value = val;
1949    pulldown.appendChild(option);
1950  },
1951  // 非日付であるか判定する
1952  isInvalidDate: function(date) {
1953    if(!date){
1954      return true;
1955    }
1956    return Number.isNaN(date.getTime());
1957  },
1958  // コンバージョンイベント送信
1959  sendSucess: function(eventName) {
1960    if(!eventName){
1961      return;
1962    }
1963    if(typeof gtag !== 'function') {
1964      return;
1965    }
1966    gtag('event', eventName);
1967  },
1968  // シェアQRコードを表示する
1969  showShareQrCode: function(url, title, buttons) {
1970    let $dialog = $('#shareQrDialog');
1971    let $div = $('#dialogQrDiv');
1972    $div.empty();
1973    $div.attr('data-url', url);
1974    let qrtext = url; //window.location.href;
1975    // qrtext = decodeURI(encodeURIComponent(qrtext));
1976    $div.qrcode({text:qrtext});
1977    prjutils.createShareQrCodeLink(title, buttons);
1978    $dialog.fadeIn('fast');
1979  },
1980  // シェアQRコードのダウンロードリンクを生成する
1981  createShareQrCodeLink: function(title, buttons) {
1982    let $dialog = $('#shareQrDialog');
1983    let $title = $dialog.find('.dialog_title');
1984    let $canvas = $('#dialogQrDiv canvas');
1985    let dataURL = $canvas[0].toDataURL("image/png");
1986    let $link = $('#dialogQrLink');
1987    if(!$title.attr('data-default')) {
1988      $title.attr('data-default', title);
1989    }
1990    if(title) {
1991      $title.text(title);
1992    } else {
1993      $title.text($title.attr('data-default'));
1994    }
1995    $link.attr('href', dataURL);
1996    // 初期状態
1997    $('#dialogQrLink').removeClass('none');
1998    $('#dialogQrUrlCopy').addClass('none');
1999
2000    // ボタン表示(デフォルト ダウンロード)
2001    if(buttons) {
2002      $('#dialogQrLink').addClass('none');
2003      for(let button of buttons) {
2004        $('#' + button).removeClass('none');
2005      }
2006    }
2007  },
2008
2009  // シェア埋め込みコードを表示する
2010  showShareHtml: function(url) {
2011      let $dialog = $('#shareDialog');
2012      let $textarea = $('#dialogHtml');
2013      // let url = window.location.href;
2014      $textarea.text('<a href="' + url + '"><img src="https://bukatsunavi.com/wp-content/themes/bukatsu/img/logo/bukatsu_fan.png" style="height:32px;"></a>');
2015      $dialog.fadeIn('fast');
2016  },
2017  // シェア埋め込みコードをクリップボードにコピーする
2018  copyShareHtml: function() {
2019      let $textarea = $('#dialogHtml');
2020      let code = $textarea.text();
2021      if (navigator.clipboard) {
2022          navigator.clipboard.writeText(code).then(function(){
2023              success_msg_html('埋め込みコードをコピーしました');
2024          });
2025      }
2026  },
2027  // QRコード表示ダイアログが示すURLをコピーする
2028  copyQrUrl: function($dialog) {
2029    let title = $dialog.find('.dialog_title').text();
2030    let $qrDiv =  $('#dialogQrDiv');
2031    let url = $qrDiv.attr('data-url');
2032    if (navigator.clipboard) {
2033        navigator.clipboard.writeText( url).then(function(){
2034            success_msg_html(title + 'をコピーしました');
2035        });
2036    }
2037  },
2038
2039  // リンクURLを表示する
2040  showLink: function(url) {
2041    let $dialog = $('#linkDialog');
2042    let $textarea = $('#dialogLink');
2043    $textarea.text(url);
2044    $dialog.fadeIn('fast');
2045  },
2046  // リンクURLをクリップボードにコピーする
2047  copyLink: function() {
2048      let $textarea = $('#dialogLink');
2049      let code = $textarea.text();
2050      if (navigator.clipboard) {
2051          navigator.clipboard.writeText(code).then(function(){
2052              success_msg_html('リンクをコピーしました');
2053          });
2054      }
2055  },
2056
2057  // 自分のパスワード変更ダイアログを開く
2058  openPasswordDialog: function() {
2059      let $form = $('#changePasswordForm');
2060      prjutils.clearFormValues($form);
2061      clearValidationMessage($form);
2062      let element = document.querySelector('.success-btn');
2063      if (!element.classList.contains('disabled')) {
2064          element.classList.add('disabled');
2065          $form.find('.success-btn').prop('disabled', true);
2066      }
2067      $('#changePasswordDiv').fadeIn('fast');
2068  },
2069
2070  // 自分のプロフィール画像編集ダイアログを開く
2071  openProfileImageDialog: function() {
2072    $('#profileImageDialog').fadeIn('fast');
2073  },
2074
2075  // 自分のプロフィールダイアログを開く
2076  openProfileDialog: function() {
2077    $('#profileDialog').fadeIn('fast');
2078  },
2079
2080  // プロフィール編集ダイアログを開く
2081  openProfileEditDialog: function(element) {
2082    prjutils.loadProfileEdit();
2083    $('#profileEditDialog').fadeIn('fast');
2084    activeValidationJ($('#changeProfileForm'));
2085    prjutils.hideDialog(element);
2086  },
2087
2088  // プロフィール編集ダイアログに保存済みの値を設定する
2089  loadProfileEdit: function() {
2090    let $form = $('#changeProfileForm');
2091    let jsonText = $('#profileEditJson').text();
2092    let naviUser = [];
2093    if (jsonText) {
2094      naviUser = JSON.parse(jsonText, true);
2095    }
2096
2097    // 画像
2098    if (naviUser['full_logo_url']) {
2099      let logoUrl = naviUser['full_logo_url'];
2100      putImage('profile_logo_url', logoUrl, '');
2101    } else {
2102      delImage('profile_logo_url');
2103    }
2104
2105    // その他の項目
2106    let fields = [
2107      'name_sei', 'name_mei', 'kana_sei', 'kana_mei', 'nick_name', 'email', 'zip_code',
2108      'prefecture_cd', 'address1', 'address2', 'address3', 'occupation_cd', 'tel'
2109    ];
2110
2111    for (let field of fields) {
2112      let value = '';
2113      if (naviUser[field] !== undefined && naviUser[field] !== null) {
2114        value = naviUser[field];
2115      }
2116      prjutils.setValue($form.find('[name="' + field + '"]'), value);
2117    }
2118  },
2119
2120
2121  // プロフィールを更新する
2122  changeProfile: function(){
2123    confirm_msg(profile_modify_caution, function(){
2124        let $form = $('#changeProfileForm');
2125        let postParam = new FormData($form.get(0));
2126        postParam.append('action', 'changeFanProfile');
2127        $.ajax({
2128            xhr: function(){
2129                let xhr = new window.XMLHttpRequest();
2130                xhr.upload.addEventListener("progress", function(evt){
2131                  prjutils.loadingImg(evt);
2132                }, false);
2133                return xhr;
2134            },
2135            type: 'POST',
2136            url: binds['ajaxurl'],
2137            data: postParam,
2138            cache: false,
2139            contentType : false,
2140            processData : false,
2141            success: function(res) {
2142                if(res['result'] === true) {
2143                    prjutils.putMsgInfo('変更が完了しました。',
2144                        function () {
2145                            location.reload();
2146                        });
2147                } else {
2148                    prjutils.loadingImgDel();
2149                    prjutils.putMsg('変更できませんでした。');
2150                }
2151            },
2152            errors: function(res) {
2153                prjutils.loadingImgDel();
2154                prjutils.putMsg('変更できませんでした。');
2155            }
2156        });
2157    });
2158  },
2159
2160  // プロフィール画像を更新する
2161  changeImageProfile: function(){
2162    confirm_msg(profile_modify_caution, function(){
2163        let $form = $('#changeProfileImageForm');
2164        let postParam = new FormData($form.get(0));
2165        postParam.append('action', 'changeImageProfile');
2166        $.ajax({
2167            xhr: prjutils.ajaxXhr,
2168            type: 'POST',
2169            url: binds['ajaxurl'],
2170            data: postParam,
2171            cache: false,
2172            contentType : false,
2173            processData : false,
2174            success: function(res) {
2175                if(res['result'] === true) {
2176                    prjutils.putMsgInfo('変更が完了しました。',
2177                        function () {
2178                            location.reload();
2179                        });
2180                } else {
2181                    prjutils.putMsg('変更できませんでした。');
2182                }
2183            },
2184            errors: function(res) {
2185                prjutils.putMsg('変更できませんでした。');
2186            }
2187        })
2188        .fail(prjutils.ajaxFail)
2189        .always(prjutils.loadingImgDel);
2190    });
2191  },
2192
2193  // 設定ダイアログを開く
2194  openSettingDialog: function() {
2195      let $dialog = $('#agentNotificationDialog');
2196      let $form = $('#agentNotificationForm');
2197      prjutils.resetDefault($form);
2198      clearValidationMessage($form);
2199      $dialog.fadeIn('fast');
2200  },
2201  // 設定ダイアログを保存する
2202  saveSettingDialog: function(){
2203    save_confirm_msg(write_caution, function () {
2204        let $form = $('#agentNotificationForm');
2205        let postParam = new FormData($form.get(0));
2206        postParam.append('action', 'saveAgentNotificationPost');
2207        $.ajax({
2208          xhr: prjutils.ajaxXhr,
2209          type: 'POST',
2210          url: binds['ajaxurl'],
2211          data: postParam,
2212          contentType : false,
2213          processData : false,
2214          success: function(res) {
2215            if(res['success'] === true) {
2216              success_msg(success_save, function() {
2217                prjutils.setValueToDefault($form);
2218                prjutils.hideDialog($form);
2219                if(postParam.get('notification_apl_push')){
2220                  $('#requestPushArea').show();
2221                } else {
2222                  $('#requestPushArea').hide();
2223                }
2224              });
2225            } else if(res['error']){
2226                alert_msg(res['error'], null);
2227            } else {
2228                alert_msg(unknown_error, null);
2229            }
2230          }
2231        })
2232        .fail(prjutils.ajaxFail)
2233        .always(prjutils.loadingImgDel);
2234    });
2235  },
2236
2237  // 退会ダイアログを開く
2238  openWithdrawalDialog: function(element) {
2239    // 継続中支援を確認
2240    prjutils.hasSubscription(
2241      // 退会に進む
2242      function() {
2243        let $form = $('#withdrawalForm');
2244        prjutils.clearFormValues($form);
2245        clearValidationMessage($form);
2246        $('#withdrawalDialog').fadeIn('fast');
2247        prjutils.hideDialog(element);
2248      }
2249    );
2250  },
2251
2252  // 継続中支援を確認
2253  hasSubscription: function(func) {
2254    let postParam = new FormData();
2255    postParam.append('action', 'validForWithdrawalPost');  
2256    $.ajax({
2257      // xhr: prjutils.ajaxXhr,
2258      type: 'POST',
2259      url: binds['ajaxurl'],
2260      data: postParam,
2261      contentType: false,
2262      processData: false,
2263      success: function(res){
2264        if(res['success'] === true) {
2265          if(func) {
2266            func();
2267          }
2268        } else if(res['error']){
2269          let msg = res['error'];
2270          let link = '';
2271          if(res['link']) {
2272            link = res['link'];
2273            let linkname = res['linkname'];
2274            msg += '\n<a href="' + link + '" target="_blank">' + linkname + '</a>';
2275          }
2276          alert_msg_html(msg);
2277        } else {
2278          alert_msg(unknown_error, null);
2279        }
2280      }
2281    })
2282    .fail(prjutils.ajaxFail)
2283  },
2284
2285  // パスワード変更を送信する
2286  changePassword: function(){
2287    password_confirm_msg(pwd_modify_caution, function(){
2288        let $form = $('#changePasswordForm');
2289        let postParam = new FormData($form.get(0));
2290        postParam.append('action', 'changeFanPassword');
2291        $.ajax({
2292            type: 'POST',
2293            url: binds['ajaxurl'],
2294            data: postParam,
2295            contentType : false,
2296            processData : false,
2297            success: function(res) {
2298                if(res['result'] === true) {
2299                    prjutils.putMsgInfo('変更が完了しました。');
2300                    prjutils.hideDialog($form);
2301                } else {
2302                    prjutils.putMsg('変更できませんでした。');
2303                }
2304            }
2305        });
2306    });
2307  },
2308  // 退会する
2309  withdrawal: function() {
2310    confirm_msg('本当に退会しますか?', function() {
2311      let $form = $('#withdrawalForm');
2312      let postParam = new FormData($form.get(0));
2313      postParam.append('action', 'withdrawalPost');
2314      $.ajax({
2315          type: 'POST',
2316          url: binds['ajaxurl'],
2317          data: postParam,
2318          contentType : false,
2319          processData : false,
2320          success: function(res) {
2321              if(res['success'] === true) {
2322                  confirm_onemsg_html('退会が完了しました。',
2323                    function(){
2324                      window.location.reload();
2325                    }
2326                  );                
2327              } else {
2328                  prjutils.putMsg('退会に失敗しました。再度ログインしてから操作してください。');
2329              }
2330          }
2331      });
2332    });
2333  },
2334  // AJAXローディング画面を表示
2335  // @param threadKey 複数スレッドでローディング待機する場合の識別キー
2336  loadingImg: function(evt, threadKey) {
2337    let percentComplete = null;
2338    if (evt && evt.lengthComputable) {
2339      percentComplete = evt.loaded / evt.total;
2340    }
2341
2342    if($('.loader').length === 0) $('body').append('<div class="loader"></div>');
2343    if($('.loader_screen').length === 0) $('body').append('<div class="loader_screen"></div>');
2344    if($('.loader_text').length === 0) $('body').append('<div class="loader_text"></div>');
2345    let perText = '処理中';
2346    if(percentComplete) {
2347      let per = Math.round(percentComplete * 100);
2348      if(per < 100) {
2349        perText = per + '%';
2350      }
2351    }
2352    $('.loader_text').text(perText);
2353    $('.loader').addClass('thread_' + threadKey);
2354  },
2355  // AJAXローディング画面を表示(GET)
2356  loadingImgByGet: function () {
2357    let percentComplete = 1;
2358    if($('.loader').length === 0) $('body').append('<div class="loader"></div>');
2359    if($('.loader_screen').length === 0) $('body').append('<div class="loader_screen"></div>');
2360    if($('.loader_text').length === 0) $('body').append('<div class="loader_text"></div>');
2361    let perText = '処理中';
2362    if(percentComplete) {
2363      let per = Math.round(percentComplete * 100);
2364      if(per < 100) {
2365        perText = per + '%';
2366      }
2367    }
2368    $('.loader_text').text(perText);
2369  },
2370  // ログイン成功後、画面遷移が完了するまでローディングを表示
2371  showLoginLoading: function() {
2372    // CAPTCHA認証完了時のajaxCompleteでローディングが消えるのを防ぐ
2373    $(document).off('ajaxComplete');
2374    prjutils.loadingImg();
2375    $('.loader_text').text('ログイン中');
2376  },
2377  // AJAXローディング画面を消去
2378  // 送信中フラグをリセット
2379  // @param threadKey このスレッドの識別キー、全スレッドが完了したら消去。空の場合かならず消去。
2380  loadingImgDel: function (threadKey) {
2381    prjutils.isSaving = false;
2382    if (typeof threadKey !== 'string') {
2383      // イベントのデフォルトパラメータを受け取った場合除外するため
2384      threadKey = null;
2385    }
2386    let $loader = $('.loader');
2387    if($loader.length > 0) {
2388      if(threadKey){
2389        $loader.removeClass('thread_' + threadKey);
2390        let threadSelector = '[class^="thread_"], [class*=" thread_"]';
2391        if($loader.is(threadSelector)) {
2392          // まだ消去しない
2393          return;
2394        }
2395      }
2396      $('.loader').remove();
2397    }
2398
2399    if($('.loader_screen').length > 0) $('.loader_screen').remove();
2400    if($('.loader_text').length > 0) $('.loader_text').remove();
2401  },
2402  // AJAXローディング画面を表示し、送信中フラグを設定(すでに送信中の場合はfalseを返す)
2403  startSaving: function() {
2404    if (prjutils.isSaving) {
2405      return false;
2406    }
2407    prjutils.isSaving = true;
2408    prjutils.loadingImgByGet();
2409    return true;
2410  },
2411  // AJAX失敗時共通処理
2412  ajaxFail: function(jqXHR, textStatus, errorThrown, customOptions) {
2413    // ページ離脱・連続操作等による通信中断(abort / ステータス0・未取得・空)はエラーログ送信・ポップアップ表示を行わない
2414    let status = jqXHR ? jqXHR.status : 0;
2415    if (textStatus === "abort" || !status || status === 0 || status === '0') {
2416      prjutils.loadingImgDel();
2417      return;
2418    }
2419
2420    if (textStatus === "timeout" || (jqXHR && (jqXHR.status === 408 || jqXHR.status === 504))) {
2421      alert_msg_html(ajax_timeout_error, null);
2422    } else {
2423      let errorCode = 'CP-' + Math.random().toString(36).substring(2, 8).padEnd(6, '0').toUpperCase();
2424      try {
2425        let postParam = new FormData();
2426        postParam.append('action', 'logUnknownErrorPost');
2427        postParam.append('error_code', errorCode);
2428        postParam.append('url', window.location.href);
2429        postParam.append('referrer', document.referrer || '');
2430        postParam.append('user_agent', navigator.userAgent);
2431        postParam.append('viewport_width', window.innerWidth);
2432        postParam.append('viewport_height', window.innerHeight);
2433        postParam.append('screen_width', screen.width);
2434        postParam.append('screen_height', screen.height);
2435        if (jqXHR) {
2436          postParam.append('ajax_status', (jqXHR.status !== undefined && jqXHR.status !== null) ? jqXHR.status : '');
2437          postParam.append('ajax_status_text', jqXHR.statusText || '');
2438          postParam.append('ajax_response_text', jqXHR.responseText ? jqXHR.responseText.substring(0, 1000) : '');
2439          postParam.append('ajax_text_status', textStatus || '');
2440          postParam.append('ajax_error_thrown', errorThrown ? errorThrown.toString() : '');
2441
2442          let opts = customOptions || jqXHR.ajaxOptions || null;
2443          if (opts) {
2444            let reqUrl = opts.url || '';
2445            let reqMethod = (opts.type || opts.method || 'GET').toUpperCase();
2446            let reqDataStr = '';
2447
2448            let rawData = opts.data;
2449            if (typeof FormData !== 'undefined' && rawData instanceof FormData) {
2450              let entries = [];
2451              try {
2452                for (let [key, value] of rawData.entries()) {
2453                  if (typeof File !== 'undefined' && value instanceof File) {
2454                    entries.push(key + '=[File: ' + value.name + ' (' + value.size + ' bytes)]');
2455                  } else {
2456                    entries.push(key + '=' + String(value));
2457                  }
2458                }
2459              } catch (e) {}
2460              reqDataStr = entries.join('&');
2461            } else if (typeof rawData === 'string') {
2462              reqDataStr = rawData;
2463            } else if (typeof rawData === 'object' && rawData !== null) {
2464              try {
2465                reqDataStr = JSON.stringify(rawData);
2466              } catch (e) {
2467                reqDataStr = String(rawData);
2468              }
2469            } else if (rawData !== undefined && rawData !== null) {
2470              reqDataStr = String(rawData);
2471            }
2472
2473            if (reqDataStr.length > 2000) {
2474              reqDataStr = reqDataStr.substring(0, 2000) + '... (truncated)';
2475            }
2476
2477            // 自己再帰防止: エラーログ送信自体が失敗した場合は再度のログ送信をスキップ
2478            if (reqDataStr.indexOf('action=logUnknownErrorPost') !== -1 || reqUrl.indexOf('logUnknownErrorPost') !== -1) {
2479              prjutils.loadingImgDel();
2480              return;
2481            }
2482
2483            postParam.append('ajax_request_url', reqUrl);
2484            postParam.append('ajax_request_method', reqMethod);
2485            postParam.append('ajax_request_data', reqDataStr);
2486          }
2487        }
2488        let ajaxUrl = (typeof binds !== 'undefined' && binds && binds['ajaxurl']) ? binds['ajaxurl'] : '/wp-admin/admin-ajax.php';
2489        $.ajax({
2490          type: 'POST',
2491          url: ajaxUrl,
2492          data: postParam,
2493          contentType: false,
2494          processData: false,
2495        });
2496      } catch (e) {
2497        console.error("Error logging failed: ", e);
2498      }
2499      let msg = unknown_system_error + '<div class="sub-text">' + errorCode + '</div>';
2500      info_msg_html(msg, null, 'ただいま一時的にご利用いただけません');
2501    }
2502    prjutils.loadingImgDel();
2503  },
2504  // AJAX保存中のローディング画面
2505  ajaxXhr: function(){
2506    let xhr = new window.XMLHttpRequest();
2507    xhr.upload.addEventListener("progress", function(evt){
2508      prjutils.loadingImg(evt);
2509    }, false);
2510    return xhr;
2511  },
2512  // ログアウトする
2513  logout: function() {
2514    let postParam = new FormData();
2515    postParam.append('action', 'logout_res');
2516    $.ajax({
2517        type: 'POST',
2518        url: binds['ajaxurl'],
2519        data: postParam,
2520        contentType: false,
2521        processData: false,
2522        success: function (res) {
2523            window.location.reload();
2524        }
2525    });
2526  },
2527  // プロフィールダイアログ:郵便番号から住所を検索し表示する
2528  autoAddressProfile: function($zipCode){
2529    AjaxZip3.onSuccess = function(){
2530      $zipCode.trigger('change');
2531    };
2532    // 住所検索
2533    AjaxZip3.zip2addr($zipCode[0],'','prefecture_cd','address1');
2534  },
2535  // メール通知設定ダイアログ(Fan)を開く
2536  openEmailNotificationDialog: function() {
2537    let $dialog = $('#emailNotificationDialog');
2538    prjutils.resetDefault($dialog);
2539    $('#emailNotificationDialog').fadeIn('fast');
2540  },
2541  // メール通知設定を更新する
2542  changeEmailNotification: function(element){
2543    let $form = $('#emailNotificationForm');
2544    let postParam = new FormData($form.get(0));
2545    postParam.append('action', 'changeEmailNotification');
2546    $.ajax({
2547        xhr: function(){
2548          let xhr = new window.XMLHttpRequest();
2549          xhr.upload.addEventListener("progress", function(evt){
2550            prjutils.loadingImg(evt);
2551          }, false);
2552          return xhr;
2553        },
2554        type: 'POST',
2555        url: binds['ajaxurl'],
2556        data: postParam,
2557        cache: false,
2558        contentType : false,
2559        processData : false,
2560        success: function(res) {
2561          prjutils.loadingImgDel();
2562          if(res['result'] === true) {
2563            prjutils.putMsgInfo('保存しました。',
2564              function () {
2565                prjutils.setValueToDefault($form);
2566                prjutils.hideDialog(element);
2567              }
2568            );
2569          } else {
2570            prjutils.putMsg('保存できませんでした。');
2571          }
2572        },
2573        errors: function(res) {
2574          prjutils.loadingImgDel();
2575          prjutils.putMsg('保存できませんでした。');
2576        }
2577    });
2578  },
2579
2580  // メール通知設定ダイアログ(Page管理)を開く
2581  openEmailNotificationPageDialog: function() {
2582    $('#emailNotificationPageDialog').fadeIn('fast');
2583  },
2584  // メール通知設定(Page管理)を更新する
2585  changeEmailNotificationPage: function(element){
2586    let $form = $('#emailNotificationPageForm');
2587    let postParam = new FormData($form.get(0));
2588    postParam.append('action', 'changeEmailNotificationPage');
2589    $.ajax({
2590        xhr: prjutils.ajaxXhr,
2591        type: 'POST',
2592        url: binds['ajaxurl'],
2593        data: postParam,
2594        cache: false,
2595        contentType : false,
2596        processData : false,
2597        success: function(res) {
2598          prjutils.loadingImgDel();
2599          if(res['result'] === true) {
2600            prjutils.putMsgInfo('保存しました。',
2601              function () {
2602                prjutils.hideDialog(element);
2603              }
2604            );
2605          } else {
2606            prjutils.putMsg('保存できませんでした。');
2607          }
2608        },
2609        errors: function(res) {
2610          prjutils.loadingImgDel();
2611          prjutils.putMsg('保存できませんでした。');
2612        }
2613    });
2614  },
2615
2616  // AI入力補助ダイアログを「情報検索」で表示する
2617  $aiInputField: null,
2618  openMyAiDialogSearch: function(schoolClubName, fieldName, $out) {
2619    let title = fieldName;
2620    let type = 'SEARCH';
2621    prjutils.openMyAiDialogCustom(title, type, schoolClubName, fieldName, $out);
2622  },
2623  // AI入力補助ダイアログを表示する
2624  openMyAiDialogCustom: function(title, type, schoolClubName, content, $out) {
2625    let action = 'aiTextPost';
2626    let $dialog = $('#myAiDialog');
2627    $dialog.find('.dialog_title').text(title);
2628    $('#myAiContent').val('');
2629    $dialog.fadeIn('fast');
2630    let postParam = new FormData();
2631    postParam.append('action', action);
2632    postParam.append('type', type);
2633    postParam.append('schoolClubName', schoolClubName);
2634    postParam.append('content', content);
2635    prjutils.$aiInputField = $out;
2636    prjutils.setTextareaHeightAuto($dialog);
2637    $.ajax({
2638      xhr: function(){
2639        let xhr = new window.XMLHttpRequest();
2640        xhr.upload.addEventListener("progress", function(evt){
2641          prjutils.loadingImg(evt);
2642        }, false);
2643        return xhr;
2644      },
2645      type: 'POST',
2646      url: binds['ajaxurl'],
2647      data: postParam,
2648      contentType : false,
2649      processData : false,
2650      success: function(res) {
2651        prjutils.loadingImgDel();
2652        if(res['success'] === true){
2653          $('#myAiContent').val(res['result']);
2654          prjutils.resetTextareaHeightAuto($('#myAiContent'));
2655        } else if(res['error']){
2656            alert_msg(res['error'], null);
2657        } else {
2658            alert_msg(unknown_error, null);
2659        }
2660      }
2661    });
2662  },
2663  // AI入力補助の内容を反映する
2664  selectAiInput: function(element) {
2665    if (prjutils.$aiInputField) {
2666      if(prjutils.$aiInputField.is('textarea')) {
2667        prjutils.$aiInputField.val($('#myAiContent').val());
2668        prjutils.resetTextareaHeightAuto(prjutils.$aiInputField);
2669      } else {
2670        prjutils.$aiInputField.val($('#myAiContent').val());
2671      }
2672    }
2673    prjutils.hideDialog(element);
2674  },
2675
2676  // 開催済のイベント・見学会・体験会を表示する
2677  showEndedItems: function(linkElement, targetSelector) {
2678    let $linkContainer = $(linkElement).parent();
2679    let $detailsContainer = $linkContainer.prev(targetSelector);
2680    $detailsContainer.find('.has-ended').show();
2681    $linkContainer.hide();
2682  },
2683
2684  // もっと見る(すべて表示)をクリックしたときに対象を表示する
2685  showAllItems: function(linkElement, targetSelector) {
2686    let $linkContainer = $(linkElement).parent();
2687    let $detailsContainer = $linkContainer.prev(targetSelector);
2688    $detailsContainer.find('.more-item').show();
2689    $linkContainer.hide();
2690  },
2691
2692  // 最近見たファンの閲覧履歴をクリアする
2693  clearReadHistory: function() {
2694    confirm_msg('閲覧履歴をすべて削除します。よろしいですか?', function() {
2695      let postParam = new FormData();
2696      postParam.append('action', 'clearReadHistoryPost');
2697      $.ajax({
2698        xhr: prjutils.ajaxXhr,
2699        type: 'POST',
2700        url: binds['ajaxurl'],
2701        data: postParam,
2702        contentType : false,
2703        processData : false,
2704        success: function(res) {
2705          prjutils.loadingImgDel();
2706          if(res['success'] === true){
2707            prjutils.putMsgInfo('閲覧履歴を削除しました。');
2708            prjutils.openMyPageDialog();
2709          } else if(res['error']){
2710            alert_msg(res['error'], null);
2711          } else {
2712            alert_msg(unknown_error, null);
2713          }
2714        }
2715      })
2716      .fail(prjutils.ajaxFail)
2717      .always(prjutils.loadingImgDel);
2718    });
2719  },
2720
2721  // マイページダイアログを表示する
2722  openMyPageDialog: function(){
2723    let title = 'マイページ';
2724    prjutils.openMyPageTitleDialog(title, 'findMyPagePost');
2725  },
2726  // マイページダイアログを表示する
2727  openMyPageTitleDialog: function(title, action) {
2728    prjutils.loadingImgByGet();
2729    $('#myPageTitle').text(title);
2730    $('#myPageDialog').fadeIn('fast');
2731    let postParam = new FormData();
2732    postParam.append('action', action);
2733    // 詳細を非表示
2734    $('#myPageDetails').addClass('none');
2735    // 動的内容をクリア
2736    $('#myPageForm .myEventDetails').children(':not(.template)').remove();
2737    $('#myPageForm .myTrialDetails').children(':not(.template)').remove();
2738    $('#myPageForm .myProjectDetails').children(':not(.template)').remove();
2739    $('#myPageForm .myFavoriteClubDetails').children(':not(.template)').remove();
2740    $('#myPageForm .myFavoriteDetails').children(':not(.template)').remove();
2741    $('#myPageForm .myHistoryDetails').children(':not(.template)').remove();
2742    $('#myPageForm .message').addClass('none');
2743    $('#myPageDialogMessage').addClass('none');
2744    $('#myPageForm .show_ended_events').hide();
2745    $('#myPageForm .show_ended_trials').hide();
2746    $('#myPageForm .show_all_projects').hide();
2747    $('#myPageForm .show_all_favorite_clubs').hide();
2748    $('#myPageForm .show_all_favorites').hide();
2749    $.ajax({
2750        xhr: prjutils.ajaxXhr,
2751        type: 'POST',
2752        url: binds['ajaxurl'],
2753        data: postParam,
2754        contentType : false,
2755        processData : false,
2756        success: function(res) {
2757            prjutils.loadingImgDel();
2758            if(res['success'] === true){
2759                let now = new Date();
2760                let isProjectNothing = true;
2761                // 詳細エリアを表示
2762                $('#myPageDetails').removeClass('none');
2763
2764                // MYイベントを表示
2765                let $areaEvent = $('#myPageForm .myEventDetails');
2766                let $templateEvent = $areaEvent.find('.template');
2767                let hasEndedEvents = false;
2768                if (res['event_list'] && res['event_list'].length > 0) {
2769                    $areaEvent.show();
2770                    $areaEvent.prev('.section_title').show();
2771                    for(var map of res['event_list']) {
2772                        let $new = prjutils.createMyEvent(map, $templateEvent);
2773                        let hasEnded = false;
2774                        if (map['end_datetime'] && map['end_datetime'] !== '0000-00-00 00:00:00' && map['end_datetime'] !== '0') {
2775                            let endStr = map['end_datetime'].replace(/-/g, '/');
2776                            let endDate = new Date(endStr);
2777                            if (!isNaN(endDate.getTime())) {
2778                                hasEnded = endDate < now;
2779                            }
2780                        }
2781                        let isCancelled = map['status_cd'] == '9';
2782                        if (hasEnded || isCancelled) {
2783                            $new.addClass('has-ended').hide();
2784                            hasEndedEvents = true;
2785                        }
2786                        $areaEvent.append($new);
2787                        isProjectNothing = false;
2788                    }
2789                } else {
2790                    $areaEvent.hide();
2791                    $areaEvent.prev('.section_title').hide();
2792                }
2793
2794                if (hasEndedEvents) {
2795                    $('#myPageForm .show_ended_events').show();
2796                } else {
2797                    $('#myPageForm .show_ended_events').hide();
2798                }
2799
2800                // MY見学・体験会を表示
2801                let $areaTrial = $('#myPageForm .myTrialDetails');
2802                let $templateTrial = $areaTrial.find('.template');
2803                let hasEndedTrials = false;
2804                if (res['trial_list'] && res['trial_list'].length > 0) {
2805                    $areaTrial.show();
2806                    $areaTrial.prev('.section_title').show();
2807                    for(var map of res['trial_list']) {
2808                        let $new = prjutils.createMyTrial(map, $templateTrial);
2809                        let hasEnded = false;
2810                        if (map['end_datetime'] && map['end_datetime'] !== '0000-00-00 00:00:00' && map['end_datetime'] !== '0') {
2811                            let endStr = map['end_datetime'].replace(/-/g, '/');
2812                            let endDate = new Date(endStr);
2813                            if (!isNaN(endDate.getTime())) {
2814                                hasEnded = endDate < now;
2815                            }
2816                        }
2817                        let isCancelled = map['status_cd'] == '9';
2818                        if (hasEnded || isCancelled) {
2819                            $new.addClass('has-ended').hide();
2820                            hasEndedTrials = true;
2821                        }
2822                        $areaTrial.append($new);
2823                        isProjectNothing = false;
2824                    }
2825                } else {
2826                    $areaTrial.hide();
2827                    $areaTrial.prev('.section_title').hide();
2828                }
2829
2830                if (hasEndedTrials) {
2831                    $('#myPageForm .show_ended_trials').show();
2832                } else {
2833                    $('#myPageForm .show_ended_trials').hide();
2834                }
2835
2836                // MYプロジェクトを表示
2837                let $areaProject = $('#myPageForm .myProjectDetails');
2838                let $templateProject = $areaProject.find('.template');
2839                let hasMoreProjects = false;
2840                if (res['project_list'] && res['project_list'].length > 0) {
2841                    $areaProject.show();
2842                    $areaProject.prev('.section_title').show();
2843                    let idx = 0;
2844                    for(var map of res['project_list']) {
2845                        let $new = prjutils.createMyProject(map, $templateProject);
2846                        if (idx >= 4) {
2847                            $new.addClass('more-item').hide();
2848                            hasMoreProjects = true;
2849                        }
2850                        $areaProject.append($new);
2851                        isProjectNothing = false;
2852                        idx++;
2853                    }
2854                } else {
2855                    $areaProject.hide();
2856                    $areaProject.prev('.section_title').hide();
2857                }
2858
2859                if (hasMoreProjects) {
2860                    $('#myPageForm .show_all_projects').show();
2861                } else {
2862                    $('#myPageForm .show_all_projects').hide();
2863                }
2864
2865                // お気に入り部活を表示
2866                let $areaFavClub = $('#myPageForm .myFavoriteClubDetails');
2867                let $templateFavClub = $areaFavClub.find('.template');
2868                let hasMoreFavClubs = false;
2869                if (res['favorite_club_list'] && res['favorite_club_list'].length > 0) {
2870                    $areaFavClub.show();
2871                    $areaFavClub.prev('.section_title').show();
2872                    let idx = 0;
2873                    for(var map of res['favorite_club_list']) {
2874                        let $new = prjutils.createFavoriteClub(map, $templateFavClub);
2875                        if (idx >= 4) {
2876                            $new.addClass('more-item').hide();
2877                            hasMoreFavClubs = true;
2878                        }
2879                        $areaFavClub.append($new);
2880                        isProjectNothing = false;
2881                        idx++;
2882                    }
2883                } else {
2884                    $areaFavClub.hide();
2885                    $areaFavClub.prev('.section_title').hide();
2886                }
2887
2888                if (hasMoreFavClubs) {
2889                    $('#myPageForm .show_all_favorite_clubs').show();
2890                } else {
2891                    $('#myPageForm .show_all_favorite_clubs').hide();
2892                }
2893
2894                // お気に入りファンを表示
2895                let $areaFavorite = $('#myPageForm .myFavoriteDetails');
2896                let $templateFavorite = $areaFavorite.find('.template');
2897                let hasMoreFavorites = false;
2898                if (res['favorite_list'] && res['favorite_list'].length > 0) {
2899                    $areaFavorite.show();
2900                    $areaFavorite.prev('.section_title').show();
2901                    let idx = 0;
2902                    for(var map of res['favorite_list']) {
2903                        let $new = prjutils.createProject(map, $templateFavorite);
2904                        if (idx >= 4) {
2905                            $new.addClass('more-item').hide();
2906                            hasMoreFavorites = true;
2907                        }
2908                        $areaFavorite.append($new);
2909                        isProjectNothing = false;
2910                        idx++;
2911                    }
2912                } else {
2913                    $areaFavorite.hide();
2914                    $areaFavorite.prev('.section_title').hide();
2915                }
2916
2917                if (hasMoreFavorites) {
2918                    $('#myPageForm .show_all_favorites').show();
2919                } else {
2920                    $('#myPageForm .show_all_favorites').hide();
2921                }
2922
2923                // 最近見たファンを表示
2924                let $areaHistory = $('#myPageForm .myHistoryDetails');
2925                let $templateHistory = $areaHistory.find('.template');
2926                if (res['read_list'] && res['read_list'].length > 0) {
2927                    $areaHistory.show();
2928                    $areaHistory.prev('.section_title').show();
2929                    let idx = 0;
2930                    for(var map of res['read_list']) {
2931                        if (idx >= 4) {
2932                            break;
2933                        }
2934                        let $new = prjutils.createProject(map, $templateHistory);
2935                        $areaHistory.append($new);
2936                        isProjectNothing = false;
2937                        idx++;
2938                    }
2939                } else {
2940                    $areaHistory.hide();
2941                    $areaHistory.prev('.section_title').hide();
2942                }
2943
2944                // 各種クリック時に data-href へ遷移させる
2945                $('#myPageDialog').off('click', '.project_box, .trial_box, .club_box').on('click', '.project_box, .trial_box, .club_box', function(e) {
2946                    let $this = $(this);
2947                    if ($(e.target).is('input[type="button"], button, a, .cancel_project, .trial_cancel, .project_cancel, .project_application_cancel')) {
2948                        return;
2949                    }
2950                    let $wrap = $this.closest('[data-href]');
2951                    let href = $wrap.attr('data-href') || $wrap.data('href');
2952                    if (href) {
2953                        if ($this.closest('#agentMyPageDialog').length) {
2954                            window.open(href, '_blank', 'noopener,noreferrer');
2955                        } else {
2956                            window.location.href = href;
2957                        }
2958                    }
2959                });
2960                if (isProjectNothing) {
2961                  $('#myPageDialogMessage').removeClass('none');
2962                } else {
2963                  $('#myPageDialogMessage').addClass('none');
2964                }
2965            } else if(res['error']){
2966                alert_msg(res['error'], null);
2967            } else {
2968                alert_msg(unknown_error, null);
2969            }
2970        }
2971    })
2972    .fail(prjutils.ajaxFail)
2973    .always(prjutils.loadingImgDel);
2974  },
2975
2976  // MY見学・体験会ダイアログを表示する
2977  openMyTrialDialog: function(){
2978    let title = 'MY見学・体験会';
2979    let zero = '申し込み済の見学・体験会がありません。';
2980    prjutils.openMyTrialsDialog(title, 'findMyTrialsPost', zero);
2981  },
2982  openMyTrialsDialog: function(title, action, zeroMessage){
2983    $('#myTrialTitle').text(title);
2984    $('#myTrialZero').text(zeroMessage);
2985    $('#myTrialDialog').fadeIn('fast');
2986    let postParam = new FormData();
2987    postParam.append('action', action);
2988    $('#myTrialForm .myTrialDetails').children(':not(.template)').remove();
2989    $('#myTrialForm .message').addClass('none');
2990    $.ajax({
2991        xhr: function(){
2992            let xhr = new window.XMLHttpRequest();
2993            xhr.upload.addEventListener("progress", function(evt){
2994              prjutils.loadingImg(evt);
2995            }, false);
2996            return xhr;
2997        },
2998        type: 'POST',
2999        url: binds['ajaxurl'],
3000        data: postParam,
3001        contentType : false,
3002        processData : false,
3003        success: function(res) {
3004            prjutils.loadingImgDel();
3005            if(res['success'] === true){
3006                let $area = $('#myTrialForm .myTrialDetails');
3007                let $template = $area.find('.template');
3008                let $new;
3009                if (res['list'].length > 0) {
3010                    for(var map of res['list']) {
3011                        $new = prjutils.createMyTrial(map, $template);
3012                        $area.append($new);
3013                    }
3014                } else {
3015                    $('#myTrialForm .message').removeClass('none');
3016                }
3017            } else if(res['error']){
3018                alert_msg(res['error'], null);
3019            } else {
3020                alert_msg(unknown_error, null);
3021            }
3022        }
3023    });
3024  },
3025
3026  // MY体験入部に1件追加表示する
3027  createMyTrial: function(map, $template) {
3028    let now = new Date();
3029    let $box = $template.clone();
3030    $box.removeClass('template');
3031    let isAnytime = map['anytime_flg'] == '1';
3032    let isStarted = false;
3033    if(map['start_datetime']) {
3034      let startDate = new Date(map['start_datetime']);
3035      isStarted = now < startDate || isAnytime;
3036    }
3037    let isCancelable = isStarted && map['status_cd'] == '1';
3038    
3039    $box.attr('data-trial_member_id', map['trial_member_id']);
3040    $box.attr('data-href', map['trial_url']);
3041    $box.find('.trial_top_image').attr('src', map['image_url']);
3042    $box.find('.trial_title').text(map['title']);
3043    $box.find('.trial_club_name').text(map['school_name'] + ' ' + map['club_name']);
3044
3045    $box.find('.trial_status_name').text(map['status_name']);
3046    if(isAnytime) {
3047      $box.find('.trial_start_date').text("随時");
3048    } else {
3049      $box.find('.trial_start_date').text(prjutils.formatYmdTime(map['start_datetime']));
3050    }
3051    $box.find('.trial_user_full_name').text(map['full_name']);
3052    $box.find('.trial_reservation_datetime').text(prjutils.formatYmdTime(map['reservation_datetime']));
3053    
3054    if (isCancelable) {
3055        $box.find('.trial_cancel').removeClass('none');
3056    }
3057    // 枠クリック
3058    $box.on('click', '.trial_box,.trial_cancel', function() {
3059      let $this = $(this);
3060      if($this.is('.trial_cancel')){
3061        return false;
3062      }
3063      let $box = $this.closest('[data-href]'); 
3064      let href = $box.attr('data-href');
3065      window.location.href = href;
3066    });
3067
3068    return $box;
3069  },
3070  // 体験入部をキャンセルする
3071  cancelTrial: function(element){
3072    let $wrap = $(element).closest('.trial_wrap');
3073    confirm_msg(cancel_trial, function(){
3074        let postParam = new FormData();
3075        postParam.append('action', 'cancelTrialPost');
3076        postParam.append('trial_member_id', $wrap.data('trial_member_id'));
3077        $.ajax({
3078            xhr: function(){
3079                let xhr = new window.XMLHttpRequest();
3080                xhr.upload.addEventListener("progress", function(evt){
3081                  prjutils.loadingImg(evt);
3082                }, false);
3083                return xhr;
3084            },
3085            type: 'POST',
3086            url: binds['ajaxurl'],
3087            data: postParam,
3088            contentType : false,
3089            processData : false,
3090            success: function(res) {
3091              prjutils.loadingImgDel();
3092                if(res['success'] === true){
3093                    prjutils.putMsgInfo('見学・体験会を取り消しました。');
3094                    if ($('#myPageDialog').is(':visible')) {
3095                        prjutils.openMyPageDialog();
3096                    } else {
3097                        prjutils.hideDialog($('#myTrialDialog'));
3098                        prjutils.openMyTrialDialog();
3099                    }
3100                } else if(res['error']){
3101                    alert_msg(res['error'], null);
3102                } else {
3103                    alert_msg(unknown_error, null);
3104                }
3105            }
3106        });
3107    });
3108  },
3109  //領収証のHTMLメールを送信する
3110  sendHtmlReceipt: function(element) {
3111    let $box = $(element).closest('.project_wrap,.agent_project_wrap');
3112    confirm_msg(send_receipt_confirm, function(){
3113      let $form = $('#ajaxform');
3114      let postParam = new FormData($form.get(0));
3115      postParam.append('action', 'sendHtmlReceiptPost');
3116      // Fan
3117      let donationId = $box.data('donation_id');
3118      if(donationId) {
3119        postParam.append('donation_id', donationId);
3120      }
3121      // Event
3122      let paymentIntent = $box.data('payment_intent');
3123      if(paymentIntent) {
3124        postParam.append('payment_intent', paymentIntent);
3125      }
3126      // Pay
3127      $.ajax({
3128        xhr: function(){
3129          let xhr = new window.XMLHttpRequest();
3130          xhr.upload.addEventListener('progress', function(evt){
3131            prjutils.loadingImg(evt);
3132          }, false);
3133          return xhr;
3134        },
3135        type: 'POST',
3136        url: binds['ajaxurl'],
3137        data: postParam,
3138        contentType: false,
3139        processData: false,
3140        success: function(res) {
3141          prjutils.loadingImgDel();
3142          if (res['success'] === true){
3143            prjutils.putMsgInfo('領収書をメールで送付しました。');
3144          } else if (res['error']) {
3145            alert_msg(res['error'], null);
3146          } else {
3147            alert_msg(unknown_error, null);
3148          }
3149        }
3150      });
3151    });
3152  },
3153
3154  // MYイベント(銀行振込待ち)ダイアログを表示する
3155  openMyBankWaitEventDialog: function(){
3156    let title = 'MYイベント(銀行振込待ち)';
3157    let zero = '銀行振込待ちのイベントがありません。';
3158    let type = 'bankwait';
3159    prjutils.openMyEventsDialog(title, 'findMyEventsPost', zero, type);
3160  },
3161  // MYイベントダイアログを表示する
3162  openMyEventDialog: function(eventScheduleId){
3163    let title = 'MYイベント';
3164    let zero = '予約済のイベントがありません。';
3165    let type= '';
3166    prjutils.openMyEventsDialog(title, 'findMyEventsPost', zero, type, eventScheduleId);
3167  },
3168  openMyEventsDialog: function(title, action, zeroMessage, type, eventScheduleId){
3169    $('#myEventTitle').text(title);
3170    $('#myEventZero').text(zeroMessage);
3171    let $dialog = $('#myEventDialog');
3172    $dialog.fadeIn('fast');
3173    $dialog.attr('data-type', type);
3174    let postParam = new FormData();
3175    postParam.append('action', action);
3176    if (type) {
3177      postParam.append('type', type);
3178    }
3179    $('#myEventForm .myEventDetails').children(':not(.template)').remove();
3180    $('#myEventForm .message').addClass('none');
3181    $('#myEventBankBlance').addClass('none');
3182    $.ajax({
3183        xhr: function(){
3184            let xhr = new window.XMLHttpRequest();
3185            xhr.upload.addEventListener("progress", function(evt){
3186              prjutils.loadingImg(evt);
3187            }, false);
3188            return xhr;
3189        },
3190        type: 'POST',
3191        url: binds['ajaxurl'],
3192        data: postParam,
3193        contentType : false,
3194        processData : false,
3195        success: function(res) {
3196            prjutils.loadingImgDel();
3197            if(res['success'] === true){
3198                let $area = $('#myEventForm .myEventDetails');
3199                let $template = $area.find('.template');
3200                let $new;
3201                if (res['list'].length > 0) {
3202                    for(var map of res['list']) {
3203                        $new = prjutils.createMyEvent(map, $template);
3204                        $area.append($new);
3205                    }
3206                    let $areaBlance = $('#myEventBankBlance');
3207                    if(res['bankwaitamount']) {
3208                      // 銀行振込表示
3209                      $areaBlance.removeClass('none');
3210                      $areaBlance.find('.blanceText').text(prjutils.toNumberFormat(res['bankwaitamount']) + "円");
3211                      prjutils.setBankNumber(res['bank']);
3212                    }
3213                    if (eventScheduleId) {
3214                      $('.myEventDetailDiv').hide();
3215                      $('.myEventDetailDiv[data-event_schedule_id=' + eventScheduleId + ']').show();
3216                    } else {
3217                      $('.myEventDetailDiv:not(.template)').show();
3218                    }
3219                } else {
3220                    $('#myEventForm .message').removeClass('none');
3221                }
3222            } else if(res['error']){
3223                alert_msg(res['error'], null);
3224            } else {
3225                alert_msg(unknown_error, null);
3226            }
3227        }
3228    });
3229  },
3230  // ログインユーザの振込先口座を埋込む
3231  loadOpenBankNumber: function(waitAmount) {
3232      let postParam = new FormData();
3233      postParam.append('action', "findStripeBankPost");
3234      $.ajax({
3235          xhr: prjutils.ajaxXhr,
3236          type: 'POST',
3237          url: binds['ajaxurl'],
3238          data: postParam,
3239          contentType : false,
3240          processData : false,
3241          success: function(res) {
3242              prjutils.loadingImgDel();
3243              if(res['success'] === true){
3244                if(res['bank']) {
3245                  prjutils.setBankNumber(res['bank'], waitAmount);
3246                  prjutils.openBankNumber();
3247                }
3248              } else if(res['error']){
3249                  alert_msg(res['error'], null);
3250              } else {
3251                  alert_msg(unknown_error, null);
3252              }
3253          }
3254      })
3255      .fail(prjutils.ajaxFail)
3256      .always(prjutils.loadingImgDel);
3257  },
3258  // 振込先口座を埋込む表示する
3259  setBankNumber: function(map, waitAmount) {
3260    let $dialog = $('#commonBankDialog');
3261    if(!map){
3262      $dialog.find('.thin_row').hide();
3263      $dialog.find('#bankEmpty').show();
3264      return;
3265    }
3266    $dialog.find('.thin_row').show();
3267    $dialog.find('#bankEmpty').hide();
3268    $dialog.find('#commonWaitAmountRow').hide();
3269    $dialog.find('.bank_code').text(map['bank_code']);
3270    $dialog.find('.bank_name').text(map['bank_name']);
3271    $dialog.find('.branch_code').text(map['branch_code']);
3272    $dialog.find('.branch_name').text(map['branch_name']);
3273    $dialog.find('.account_type_name').text(map['account_type_name']);
3274    $dialog.find('.account_number').text(map['account_number']);
3275    $dialog.find('.account_holder_name').text(map['account_holder_name']);
3276    if(waitAmount > "0") {
3277      $dialog.find('.wait_amount').text(waitAmount);
3278      $dialog.find('#commonWaitAmountRow').show();
3279    }
3280  },
3281  // 振込先口座を表示する
3282  openBankNumber: function(){
3283    prjutils.showDialog($('#commonBankDialog'));
3284  },
3285  // MYイベントに1件追加表示する
3286  createMyEvent: function(map, $template) {
3287    let now = new Date();
3288    let $box = $template.clone();
3289    $box.removeClass('template');
3290    let isBeforeStart = false;
3291    if(map['start_datetime']) {
3292      let startDate = new Date(map['start_datetime'].replace(/-/g, '/'));
3293      isBeforeStart = !isNaN(startDate.getTime()) && now < startDate;
3294    }
3295    let isBankWaitingCancelable = map['payments_status'] == '1' && map['payments_method'] == '2'; // 未決済,銀行振込
3296    const isPaid = map['payments_status'] == '2';
3297    // 既存申込はstatus_cdがNULLのため、NULLも申込済みとして扱う。
3298    const isReserved = map['status_cd'] == null || map['status_cd'] == '1';
3299    const isCancelled = map['status_cd'] == '9';
3300    const isApplicationCancelable = isBeforeStart && isReserved && isPaid;
3301    if (isCancelled) {
3302      $box.addClass('is-cancelled');
3303    }
3304    $box.attr('data-project_member_id', map['member_id']);
3305    $box.attr('data-event_schedule_id', map['event_schedule_id']);
3306    $box.attr('data-event_detail_id', map['event_detail_id']);
3307    $box.attr('data-event_member_id', map['event_member_id']);
3308    $box.attr('data-club_cd', map['club_cd']);
3309    $box.attr('data-opening_datetime', map['opening_datetime']);
3310    $box.attr('data-payment_intent', map['payment_intent']);
3311    $box.attr('data-amount', map['amount']);
3312    $box.attr('data-href', map['project_url']);
3313
3314    $box.find('.project_top_image').attr('src', map['project_image_url']);
3315    $box.find('.project_title').text(map['title']);
3316    $box.find('.project_club_name').text(map['school_name'] + ' ' + map['club_name']);
3317
3318    $box.find('.project_status_name').text(isCancelled ? 'キャンセル済' : map['status_name']);
3319    $box.find('.project_start_date').text(prjutils.formatYmdTime(map['start_datetime']));
3320    $box.find('.project_user_full_name').text(map['full_name']);
3321    $box.find('.project_reservation_datetime').text(prjutils.formatYmdTime(map['reservation_datetime']));
3322    $box.find('.project_ticketno').text(map['ticket_range']);
3323    if(map['isBankWaiting']) {
3324      $box.find('.project_ticketno_div').addClass('none');
3325      $box.find('.bank_waiting').removeClass('none');
3326      $box.find('.waiting_amount').text(prjutils.toNumberFormat(map['amount_remaining']));
3327    }
3328
3329    if (isBankWaitingCancelable) {
3330        $box.find('.project_cancel').removeClass('none');
3331    }
3332    if (isApplicationCancelable) {
3333      $box.find('.project_application_cancel').removeClass('none');
3334    }
3335    if (isPaid && !isCancelled) {
3336      $box.find('.project_receptionQrCode').removeClass('none');
3337    }
3338    if(isPaid && map['payment_intent']) {
3339      $box.find('.button_sendReceipt').removeClass('none');
3340    }
3341    return $box;
3342  },
3343  // イベントの申込をキャンセルする(決済済みの場合も返金は行わない)
3344  cancelEventApplication: function(element){
3345    let $wrap = $(element).closest('.project_wrap');
3346    let amount = Number($wrap.data('amount')) || 0;
3347    if (amount > 0) {
3348      prjutils.confirmPaidEventCancellation($wrap);
3349      return;
3350    }
3351    confirm_msg(cancel_event, function(){
3352      prjutils.executeEventApplicationCancel($wrap);
3353    });
3354  },
3355  // 既存の確認ポップアップに返金不可への同意チェックを追加する
3356  confirmPaidEventCancellation: function($wrap){
3357    let html = '<div>イベントの申込をキャンセルします。</div>'
3358      + '<div>キャンセル後も、お支払い済みの参加費は返金されません。</div>'
3359      + '<div style="margin-top:16px;">'
3360      + '<input type="checkbox" id="paidEventCancelAgree" autocomplete="off">'
3361      + '<label for="paidEventCancelAgree" style="margin-left:6px;">
3361返金されないことに同意する</label>'
3362      + '</div>';
3363    let confirmPromise = swal({
3364      title: '',
3365      type: 'info',
3366      html: html,
3367      showCancelButton: true,
3368      confirmButtonText: 'はい',
3369      confirmButtonColor: '#286090',
3370      cancelButtonText: 'キャンセル'
3371    });
3372
3373    // 読み込まれているSweetAlertの版に合わせ、両方の確認ボタンclassへ対応する。
3374    setTimeout(function(){
3375      let $agree = $('#paidEventCancelAgree');
3376      let $confirmButton = $('.sweet-alert button.confirm, .swal2-confirm');
3377      $confirmButton.prop('disabled', true);
3378      $agree.on('change', function(){
3379        $confirmButton.prop('disabled', !$agree.prop('checked'));
3380      });
3381    }, 0);
3382
3383    confirmPromise.then(function(){
3384      prjutils.executeEventApplicationCancel($wrap);
3385    }).catch(swal.noop);
3386  },
3387  // イベント申込キャンセルAPIを実行する
3388  executeEventApplicationCancel: function($wrap){
3389      let postParam = new FormData();
3390      postParam.append('action', 'cancelEventApplicationPost');
3391      postParam.append('event_member_id', $wrap.data('event_member_id'));
3392      $.ajax({
3393        xhr: prjutils.ajaxXhr,
3394        type: 'POST',
3395        url: binds['ajaxurl'],
3396        data: postParam,
3397        contentType: false,
3398        processData: false,
3399        success: function(res) {
3400          if(res['success'] === true){
3401            prjutils.putMsgInfo('イベントの申込をキャンセルしました。');
3402            if ($('#myPageDialog').is(':visible')) {
3403              prjutils.openMyPageDialog();
3404            } else {
3405              let eventScheduleId = $wrap.data('event_schedule_id');
3406              prjutils.hideDialog($('#myEventDialog'));
3407              prjutils.openMyEventDialog(eventScheduleId);
3408            }
3409          } else if(res['error']){
3410            alert_msg(res['error'], null);
3411          } else {
3412            alert_msg(unknown_error, null);
3413          }
3414        }
3415      })
3416      .fail(prjutils.ajaxFail)
3417      .always(prjutils.loadingImgDel);
3418  },
3419  // MYプロジェクトに1件追加表示する
3420  createMyProject: function(map, $template) {
3421    let $box = $template.clone();
3422    $box.removeClass('template');
3423    let isReturn = map['return_flg'] == '1';
3424    let isDelivery = map['delivery_flg'] == '1';
3425    let isContinuation = false;
3426    let donationTypeCss = '';
3427    if(map['donation_type'] == '10') {
3428        donationTypeCss = 'continuation';
3429        isContinuation = true;
3430    } else if(map['donation_type'] == '20'){
3431        donationTypeCss = 'continuation sponsor';
3432        isContinuation = true;
3433    }
3434    let cancelDate = map['cancel_date'] ? ' ~ 終了' : '';
3435    let projectImageUrl = map['project_image_url'];
3436    let imageJson = map['image_json'] ? JSON.parse(map['image_json']) : [];
3437    if (imageJson.length > 0) {
3438        projectImageUrl = getImageUrl(imageJson[0]);
3439    }
3440
3441    $box.data('donation_id', map['id']);
3442    $box.data('href', map['project_url']);
3443    $box.data('payments_method', map['payments_method']);
3444
3445    $box.addClass(donationTypeCss);
3446    $box.find('.project_top_image').attr('src', projectImageUrl);
3447    $box.find('.project_tag').text(map['donation_type_name']);
3448    $box.find('.project_title').text(map['title']);
3449    $box.find('.project_club_name').text(map['school_name'] + ' ' + map['club_name']);
3450    $box.find('.project_amount .value').text(prjutils.toNumberFormat(map['amount']));
3451    $box.find('.project_settlement_date .value').text(map['settlement_date'].split(' ')[0] + cancelDate);
3452
3453    if (isReturn) {
3454        let $title = $box.find('.project_return_title');
3455        $title.removeClass('none');
3456        $title.find('.value').text(map['detail_title']);
3457    }
3458    if (isDelivery) {
3459        let $address = $box.find('.project_delivary_address');
3460        $address.find('.value').text(map['address']);
3461        $address.removeClass('none');
3462
3463        let $addressTel = $box.find('.project_delivary_tel');
3464        $addressTel.find('.value').text(map['address_tel']);
3465        $addressTel.removeClass('none');
3466
3467        let $addressName = $box.find('.project_delivary_name');
3468        $addressName.find('.value').text(map['address_name']);
3469        $addressName.removeClass('none');
3470    }
3471    if (isContinuation && !cancelDate) {
3472        $box.find('.project_cancel').removeClass('none');
3473    }
3474    return $box;
3475  },
3476  // プロジェクトリスト(お気に入り・閲覧履歴)に1件追加表示する
3477  createProject: function(map, $template) {
3478    let $box = $template.clone();
3479    $box.removeClass('template');
3480    let isContinuation = false;
3481    let donationTypeCss = '';
3482    if(map['donation_type'] == '10') {
3483        donationTypeCss = 'continuation';
3484        isContinuation = true;
3485    } else if(map['donation_type'] == '20'){
3486        donationTypeCss = 'continuation sponsor';
3487        isContinuation = true;
3488    }
3489
3490    $box.data('donation_id', map['id']);
3491    $box.data('href', map['project_url']);
3492    $box.addClass(donationTypeCss);
3493    $box.find('.project_top_image').attr('src', map['project_image_url']);
3494
3495    $box.find('.project_tag').text(map['donation_type_name']);
3496    $box.find('.project_title').text(map['title']);
3497    $box.find('.project_club_name').text(map['school_name'] + ' ' + map['club_name']);
3498    $box.find('.project_current_amount .value').text(prjutils.toNumberFormat(map['total_amount']) + '円');
3499    $box.find('.project_members .value').text(prjutils.toNumberFormat(map['donate_cnt']) + '人');
3500    $box.find('.project_remaining').html(map['remaining_tag']);
3501
3502    let progress = map['progress'];
3503    $box.find('.project_target_amount').text(prjutils.toNumberFormat(progress) + '%');
3504    let progressWidth = Math.min(Math.max(4, progress), 100);
3505    let $progressBar = $box.find('.project_progress');
3506    $progressBar.css('width', progressWidth + '%');
3507    if(progress <= 0){
3508        $progressBar.hide();
3509    } else if(progress >= 100){
3510        $progressBar.addClass('goal');
3511    }
3512    return $box;
3513  },
3514  // お気に入り部活リストに1件追加表示する
3515  createFavoriteClub: function(map, $template) {
3516    let $box = $template.clone();
3517    $box.removeClass('template');
3518    $box.data('href', map['url']);
3519    $box.find('.club_top_image').attr('src', map['logo_url']);
3520    $box.find('.club_school_name').text(map['school_name']);
3521    $box.find('.club_name').text(map['club_name']);
3522    return $box;
3523  },
3524  // イベントをキャンセルする
3525  cancelEvent: function(element){
3526    let $wrap = $(element).closest('.project_wrap');
3527    confirm_msg(cancel_bankwait, function(){
3528        let postParam = new FormData();
3529        postParam.append('action', 'cancelBankPaymentPost');
3530        postParam.append('payment_intent', $wrap.data('payment_intent'));
3531        $.ajax({
3532            xhr: function(){
3533                let xhr = new window.XMLHttpRequest();
3534                xhr.upload.addEventListener("progress", function(evt){
3535                  prjutils.loadingImg(evt);
3536                }, false);
3537                return xhr;
3538            },
3539            type: 'POST',
3540            url: binds['ajaxurl'],
3541            data: postParam,
3542            contentType : false,
3543            processData : false,
3544            success: function(res) {
3545              prjutils.loadingImgDel();
3546                if(res['success'] === true){
3547                    prjutils.putMsgInfo('銀行振込待ちをキャンセルしました。');
3548                    prjutils.hideDialog($('#myEventDialog'));
3549                    let type = $('#myEventDialog').attr('data-type');
3550                    if(type == 'bankwait') {
3551                      prjutils.openMyBankWaitEventDialog();
3552                    } else {
3553                      prjutils.openMyEventDialog();
3554                    }
3555                } else if(res['error']){
3556                    alert_msg(res['error'], null);
3557                } else {
3558                    alert_msg(unknown_error, null);
3559                }
3560            }
3561        });
3562    });
3563  },
3564
3565  // 受付用の入場チケット(QRコード)を表示する
3566  showReceptionQrCode: function(element) {
3567    $('#receptionQrCodePictureArea').empty();
3568    prjutils.disableNextReceptionQrCodeSwitchingBtn();
3569    prjutils.disablePrevReceptionQrCodeSwitchingBtn();
3570    let $wrap = $(element).closest('.project_wrap');
3571    $('#receptionQrCodeTitle').text($wrap.find('.project_title').text());
3572    $('#receptionQrCodeStartTime').text($wrap.data('opening_datetime').split(' ')[1].substring(0, 5));
3573
3574    const eventMemberId = $wrap.data('event_member_id');
3575    const clubCd = $wrap.data('club_cd');
3576
3577    var $form = $('#formDialogRequest');
3578    var postParam = new FormData($form.get(0));
3579    postParam.append('event_member_id', eventMemberId);
3580    postParam.append('club_cd', clubCd);
3581    postParam.append('action', 'showReceptionQrCodePost');
3582    $.ajax({
3583      xhr: function(){
3584        let xhr = new window.XMLHttpRequest();
3585        xhr.upload.addEventListener("progress", function(evt){
3586          prjutils.loadingImg(evt);
3587        }, false);
3588        return xhr;
3589      },
3590      type: 'POST',
3591      url: binds['ajaxurl'],
3592      data: postParam,
3593      contentType : false,
3594      processData : false,
3595      success: function(res) {
3596        if(res['error']) {
3597          prjutils.loadingImgDel();
3598          alert_msg(res['error'], null);
3599          return false;
3600        }
3601        if(res['result'] === true && res['qrTextArray'].length > 0) {
3602          if (res['qrTextArray'].length > 1) {
3603            prjutils.enableNextReceptionQrCodeSwitchingBtn();
3604          }
3605          $('#receptionQrCodePictureArea').qrcode({text: res['qrTextArray'][0]});
3606          if (res['acceptedFlgArray'][0] === '1') {
3607            $('#receptionQrCodeAcceptedMark').removeClass('none');
3608          }
3609          $('#receptionQrCodeNumber').text(res['ticketNo']);
3610          $('#receptionQrCodeArrayLength').text(res['qrTextArray'].length);
3611          $('#receptionQrCodeArrayFirstTicketNo').text(res['ticketNo']);
3612          prjutils.showDialog($('#commonReceptionQrCode'));
3613          const $receptionQrCodeTicketIdArray = $('#receptionQrCodeTicketIdArray');
3614          const $receptionQrCodeTicketAcceptedFlgArray = $('#receptionQrCodeTicketAcceptedFlgArray');
3615          $receptionQrCodeTicketIdArray.empty();
3616          $receptionQrCodeTicketAcceptedFlgArray.empty();
3617          for (var i = 0; i < res['qrTextArray'].length; i++) {
3618            $receptionQrCodeTicketIdArray.append('<div id="receptionQrCodeTicketId_' + (parseInt(res['ticketNo']) + i) + '">' + res['qrTextArray'][i] + '</div>');
3619            $receptionQrCodeTicketAcceptedFlgArray.append('<div id="receptionQrCodeTicketAcceptedFlg_' + (parseInt(res['ticketNo']) + i) + '">' + res['acceptedFlgArray'][i] + '</div>');
3620          }
3621          prjutils.loadingImgDel();
3622        } else {
3623          prjutils.loadingImgDel();
3624          alert_msg('チケットが表示できません。', null);
3625        }
3626      }
3627    });
3628  },
3629  // ひとつ前の受付用の入場チケット(QRコード)を表示する
3630  prevReceptionQrCode: function() {
3631    var receptionQrCodeNumber = Number($('#receptionQrCodeNumber').text()) - 1;
3632    const firstTicketNo = Number($('#receptionQrCodeArrayFirstTicketNo').text());
3633    if (receptionQrCodeNumber < firstTicketNo) {
3634      return;
3635    } else if (receptionQrCodeNumber === firstTicketNo) {
3636      prjutils.disablePrevReceptionQrCodeSwitchingBtn();
3637    }
3638    $('#receptionQrCodePictureArea').empty();
3639    prjutils.enableNextReceptionQrCodeSwitchingBtn();
3640    $('#receptionQrCodePictureArea').qrcode({text: $('#receptionQrCodeTicketId_' + receptionQrCodeNumber).text()});
3641    $('#receptionQrCodeNumber').text(receptionQrCodeNumber);
3642    if ($('#receptionQrCodeTicketAcceptedFlg_' + receptionQrCodeNumber).text() === '1') {
3643      $('#receptionQrCodeAcceptedMark').removeClass('none');
3644    } else {
3645      $('#receptionQrCodeAcceptedMark').addClass('none');
3646    }
3647  },
3648  // 次の受付用の入場チケット(QRコード)を表示する
3649  nextReceptionQrCode: function() {
3650    var receptionQrCodeNumber = Number($('#receptionQrCodeNumber').text()) + 1;
3651    const firstTicketNo = Number($('#receptionQrCodeArrayFirstTicketNo').text());
3652    if ( Number($('#receptionQrCodeArrayLength').text()) + firstTicketNo - 1 < receptionQrCodeNumber) {
3653      return;
3654    } else if (receptionQrCodeNumber === (Number($('#receptionQrCodeArrayLength').text()) + firstTicketNo - 1)) {
3655      prjutils.disableNextReceptionQrCodeSwitchingBtn();
3656    }
3657    $('#receptionQrCodePictureArea').empty();
3658    prjutils.enablePrevReceptionQrCodeSwitchingBtn();
3659    $('#receptionQrCodePictureArea').qrcode({text: $('#receptionQrCodeTicketId_' + receptionQrCodeNumber).text()});
3660    $('#receptionQrCodeNumber').text(receptionQrCodeNumber);
3661    if ($('#receptionQrCodeTicketAcceptedFlg_' + receptionQrCodeNumber).text() === '1') {
3662      $('#receptionQrCodeAcceptedMark').removeClass('none');
3663    } else {
3664      $('#receptionQrCodeAcceptedMark').addClass('none');
3665    }
3666  },
3667  // 前のQRコード表示ボタンを活性化する
3668  enablePrevReceptionQrCodeSwitchingBtn: function () {
3669    $('#prevReceptionQrCodeSwitchingBtn').css({'cssText': 'color:#000000 !important; border-radius: 0rem !important;'});
3670  },
3671  // 前のQRコード表示ボタンを非活性化する
3672  disablePrevReceptionQrCodeSwitchingBtn: function () {
3673    $('#prevReceptionQrCodeSwitchingBtn').css({'cssText': 'color:#cccccc !important; border-radius: 0rem !important;'});
3674  },
3675  // 次のQRコード表示ボタンを活性化する
3676  enableNextReceptionQrCodeSwitchingBtn: function () {
3677    $('#nextReceptionQrCodeSwitchingBtn').css({'cssText': 'color:#000000 !important; border-radius: 0rem !important;'});
3678  },
3679  // 次のQRコード表示ボタンを非活性化する
3680  disableNextReceptionQrCodeSwitchingBtn: function () {
3681    $('#nextReceptionQrCodeSwitchingBtn').css({'cssText': 'color:#cccccc !important; border-radius: 0rem !important;'});
3682  },
3683  // 直URLにて入場チケット(QRコード)を表示する
3684  showDirectReceptionQrCode: function(qrText) {
3685    $('#footer').hide();
3686    $('#directReceptionQrCodePictureArea').qrcode(qrText);
3687    $('#directReceptionQrCodeStartTime').text($('#directReceptionQrCodeOpeningDateTime').text().split(' ')[1].substring(0, 5));
3688    $('#bottomMenu').css({'cssText': 'display: none !important;'});
3689  },
3690
3691  // 指定要素のテキストをクリップボードにコピーする
3692  copyText: function(id) {
3693    let $item = $('#' + id);
3694    let text = $item.text();
3695    if (navigator.clipboard) {
3696      navigator.clipboard.writeText(text).then(function(){
3697        prjutils.popTemporaryMessage('クリップボードにコピーしました');
3698      });
3699    }
3700  },
3701  // 一時的なメッセージを表示する
3702  popTemporaryMessage: function(message) {
3703    let $msgItem = $('<div class="pop_message">' + message + '</div>');
3704    let $body = $('body');
3705    $body.append($msgItem);
3706    setTimeout(function(){
3707      $msgItem.fadeOut(500,function(){
3708        $msgItem.remove()
3709      });
3710    }, 1500);
3711  },
3712
3713  // arrayをCSVとしてダウンロードする
3714  downloadCsvByArray: function(list, filename) {
3715    csvString = list.map(item => prjutils.joinAny(item, ',')).join("\n");
3716    let binary = new Blob([csvString], {type: "text/csv"});
3717    let anchor = document.createElement("a");
3718    anchor.href = window.URL.createObjectURL(binary);
3719    anchor.download = filename;
3720    anchor.click();
3721  },
3722
3723  // アクセス状況表の内容を表示
3724  setAccessesTable: function(list) {
3725    let $body = $('#accessesTableBody');
3726    $body.children('tr:not(.template)').remove();
3727    let $template = $body.find('tr.template');
3728    let sumPageVies = 0;
3729    let sumTotalUsers = 0;
3730    let sumNewUsers = 0;
3731    let found = false;
3732    for(let idx in list) {
3733      let $tr = $template.clone();
3734      $tr.removeClass('template');
3735      let item = list[idx];
3736      // ページ
3737      let $pageTitle = $tr.find('.page_title');
3738      $pageTitle.text(item['page_title']);
3739      let $pageLink = $tr.find('.page_link');
3740      $pageLink.attr('href', item['full_url']);
3741      // 各件数
3742      $tr.children('.page_views').text(prjutils.toNumberFormat(item['page_views']));
3743      sumPageVies += parseInt(item['page_views'], 10);
3744      $tr.children('.total_users').text(prjutils.toNumberFormat(item['total_users']));
3745      sumTotalUsers += parseInt(item['total_users'], 10);
3746      $tr.children('.new_users').text(prjutils.toNumberFormat(item['new_users']));
3747      sumNewUsers += parseInt(item['new_users'], 10);
3748      $body.append($tr);
3749      found = true;
3750    }
3751    // 合計値
3752    $('#accessesSumPageViews').text(prjutils.toNumberFormat(sumPageVies));
3753    $('#accessesSumTotalUsers').text(prjutils.toNumberFormat(sumTotalUsers));
3754    $('#accessesSumNewUsers').text(prjutils.toNumberFormat(sumNewUsers));
3755    if(found) {
3756      $('#accessesTable').show();
3757      $('#accessesNotFound').hide();
3758    } else {
3759      $('#accessesTable').hide();
3760      $('#accessesNotFound').show();
3761    }
3762  },
3763
3764  // map,arrayをキーワードでjoinする
3765  joinAny: function(item, keyword) {
3766    if(Array.isArray(item)) {
3767      return item.join(keyword);
3768    }
3769    let res = '';
3770    let keyList = Object.keys(item);
3771    for(let i of keyList) {
3772      if(res !== '') {
3773        res += keyword;
3774      }
3775      res += item[i];
3776    }
3777    return res;
3778  },
3779
3780  // SPサイズで表示しているか検証する
3781  isSP: function() {
3782    return $(window).width() <= 767;
3783  },
3784  
3785  // SPデバイスであるかを検証する
3786  isMobileDevice: function() {
3787    let width = window.screen.width;
3788    return width <= 767;
3789  },
3790
3791  // Pageの部活サイドメニューを開く
3792  openClubMenu: function() {
3793    $('#sideMenuList').find('.clubNemu').show();
3794    $('#btnClubOpen').hide();
3795    $('#btnClubClose').show();
3796  },
3797  // Pageの部活サイドメニューを閉じる
3798  closeClubMenu: function() {
3799    let clubs = $('#sideMenuList').find('.clubNemu').length;
3800    if (clubs <= 2) {
3801      // 全表示固定
3802      $('#sideMenuList').find('.clubNemu').show();
3803      $('#btnClubOpen').hide();
3804      $('#btnClubClose').hide();  
3805      return;
3806    }
3807    $('#sideMenuList').find('.clubNemu:not(.current):not(.favorite)').hide();
3808    $('#sideMenuList').find('.clubNemu.favorite').show();
3809    $('#btnClubOpen').show();
3810    $('#btnClubClose').hide();
3811  },
3812
3813  // 部員選択ダイアログ 開く
3814  memberSelectionFunc: null,
3815  $memberSelectionSetItem: null,
3816  openMemberSelection: function(eventElement, allMemberList, excludeIdList, selectedFunc, memberIdClass) {
3817    prjutils.memberSelectionFunc = selectedFunc;
3818    let $this = $(eventElement);
3819    let $dialog = $('#commonMemberSelectDialog');
3820    let $form = $('#commonMemberSelectForm');
3821    let $box = $(eventElement).closest('.member_selction_box');
3822    let isReadOnly = $this.is('.readonly');
3823    if(!memberIdClass) {
3824      memberIdClass = 'member_id_csv';
3825    }
3826
3827    prjutils.$memberSelectionSetItem = $box.find('.' + memberIdClass);
3828    let selectedCsv = prjutils.$memberSelectionSetItem.val();
3829    let arrSelectedId = selectedCsv.split(',');
3830
3831    // 読み取り専用
3832    if(isReadOnly) {
3833      $dialog.find('.btn-save').addClass('none_imp');
3834    } else {
3835      $dialog.find('.btn-save').removeClass('none_imp');
3836    }
3837    
3838    // 開く
3839    $('#commonMemberSelectList').empty();
3840    prjutils.showDialog($dialog);
3841    prjutils.clearFormValues($dialog);
3842    clearValidationMessage($dialog);
3843    $('#commonMemberSelectFilterArea input[type="checkbox"]').prop('checked', true);
3844    
3845    if(allMemberList) {
3846      for (const allMemberIndex in allMemberList) {
3847        let mebmerId = allMemberList[allMemberIndex]['member_id'];
3848        // 除外指定で除外
3849        if(excludeIdList && excludeIdList.includes(mebmerId) && !arrSelectedId.includes(mebmerId)) {
3850          // 除外対象でも選択済は表示する
3851          continue;
3852        }
3853        
3854        let $template = $('#commonMemberSelectLineTemplate').clone();
3855        let $groupMemberSelectCheckbox = $template.find('.group_member_select_checkbox');
3856        let $groupMemberSelectCheckboxLabel = $template.find('.group_member_select_checkbox_label');
3857        // 部員
3858        $groupMemberSelectCheckboxLabel.text(allMemberList[allMemberIndex]['name'] + ' / ' + allMemberList[allMemberIndex]['school_year']);
3859        const checkboxId = "common_group_member_select_checkbox_" + allMemberList[allMemberIndex]['member_id'];
3860        $groupMemberSelectCheckboxLabel.attr('for', checkboxId);
3861        $groupMemberSelectCheckboxLabel.attr('data-jh_grade', allMemberList[allMemberIndex]['jh_grade']);
3862        $groupMemberSelectCheckbox.attr('id', checkboxId);
3863        $groupMemberSelectCheckbox.attr('data-jh_grade', allMemberList[allMemberIndex]['jh_grade']);
3864        $groupMemberSelectCheckbox.val(allMemberList[allMemberIndex]['member_id']);
3865        $template.attr('id', "groupMemberSelectLineTemplate_" + allMemberList[allMemberIndex]['member_id']);
3866        $template.removeClass('hide');
3867        let allMemberId = allMemberList[allMemberIndex]['member_id'];
3868        let isSelected = arrSelectedId.includes(allMemberId);
3869        $groupMemberSelectCheckbox.prop('checked', isSelected);
3870        $('#commonMemberSelectList').append($template);
3871      }
3872    }
3873    prjutils.changeFilterMember();
3874  },
3875  // 部員選択ダイアログを決定して閉じる
3876  selectMemberDialog: function(eventElement) {
3877    let $form = $('#commonMemberSelectForm');
3878
3879    if(prjutils.$memberSelectionSetItem) {
3880      let $memberList = $('#commonMemberSelectList');
3881      let $checkedList = $memberList.find(':not(.none_imp) .group_member_select_checkbox:checked');
3882      let selectedArray = [];
3883      $checkedList.each(function () {
3884        selectedArray.push($(this).val());
3885      });
3886      let csv = selectedArray.join(',');
3887      prjutils.$memberSelectionSetItem.val(csv);
3888    }
3889    if (prjutils.memberSelectionFunc) {
3890      prjutils.memberSelectionFunc();
3891    }
3892    prjutils.hideDialog($form);
3893  },
3894
3895  // グループメンバー選択ダイアログ メンバーフィルタ
3896  changeFilterMember: function () {
3897    $('.group_member_select_checkbox_label[data-jh_grade=99]').closest('.group_member_select_line').addClass('none_imp');
3898    for (let i = 1; i < 13; i++){
3899      let $checkbox = $('#common_group_member_filter_checkbox_' + i);
3900      if ($checkbox.prop('checked')) {
3901        $('.group_member_select_checkbox_label[data-jh_grade=' + i + ']').closest('.group_member_select_line').removeClass('none_imp');
3902      } else {
3903        $('.group_member_select_checkbox_label[data-jh_grade=' + i + ']').closest('.group_member_select_line').addClass('none_imp');
3904      }
3905    }
3906  },
3907  // グループメンバー選択ダイアログ フィルタ結果を全選択
3908  changeFilterMemberAll: function() {
3909    let $chkbox = $('#common_group_member_filter_all');
3910    let $list = $('#commonMemberSelectList');
3911    $list.find('input[type="checkbox"]:enabled').prop('checked', false);
3912    if($chkbox.is(':checked')) {
3913      // 全選択
3914      $list.find(':not(.none_imp) input[type="checkbox"]:enabled').prop('checked', true);
3915    } else {
3916      // 全解除
3917      $list.find(':not(.none_imp) input[type="checkbox"]:enabled').prop('checked', false);
3918    }
3919  },
3920
3921  // 対象が表示範囲内であるか判定する
3922  isShowArea: function($item) {
3923    let isShow = false;
3924    if($item.length == 0) {
3925      return isShow;
3926    }
3927    let $window = $(window);
3928    let elementTop = $item.offset().top; // 上端
3929    let elementBottom = elementTop + $item.outerHeight(); // 下端
3930    let windowTop = $window.scrollTop(); // ウィンドウスクロール位置
3931    let windowBottom = windowTop + $window.height(); // ウィンドウ下端
3932    if (elementBottom > windowTop && elementTop < windowBottom) {
3933      isShow = true;
3934    }
3935    return isShow;
3936  },
3937  //ダブルクリックによる文字選択を回避
3938  setDoubleClickSelectDisabled: function() {
3939    let $body = $('body');
3940    $body.on('click', function(evt){
3941      if (evt.detail > 1) {
3942        // 選択を解除
3943        if (window.getSelection) {
3944          window.getSelection().removeAllRanges();
3945        }
3946      }
3947    });
3948  },
3949  // ページングをスクロールで発火(.paging_auto)
3950  // 追加先は.paging_auto_list
3951  // 1画面に一個のみ対応
3952  isPagingAutoLoading: false,
3953  setPagingAuto: function() {
3954    let $pagingAuto = $('.paging_auto');
3955    if($pagingAuto.length == 0) {
3956      return;
3957    }
3958    let $pagingAutoList = $('.paging_auto_list');
3959    let $window = $(window);
3960    $window.on('scroll', function() {
3961      if(prjutils.isPagingAutoLoading) {
3962        return;
3963      }
3964      const windowHeight = $window.height();
3965      const scrollPosition = $window.scrollTop();
3966      const elementTop = $pagingAuto.offset().top;    
3967      if (elementTop < scrollPosition + windowHeight) {
3968        prjutils.isPagingAutoLoading = true;
3969        // 下までスクロールした
3970        $pagingAuto.append($('<div class="paging_auto_loading"><div class="spinner-border text-secondary"></div></div>'));
3971        prjutils.loadNextPage($pagingAuto, $pagingAutoList);
3972      }
3973    });
3974  },
3975  // ページング要素の次ページを下に埋め込む
3976  loadNextPage: function($pagingAuto, $listArea) {
3977    let $pageNext = $pagingAuto.find('.page_next');
3978    if($pageNext.length == 0) {
3979      // 既に最終ページ
3980      $pagingAuto.find('.paging_auto_loading').remove();
3981      return;
3982    }
3983    let js = $pageNext.attr('onclick');
3984    js = js.replace("location.href='", "");
3985    let url = js.replace("'", "");
3986
3987    $.ajax({
3988        type: 'GET',
3989        url: url,
3990        contentType: false,
3991        processData: false,
3992        success: function (res) {
3993          let $res = $(res);
3994          // データ埋込
3995          let $list = $res.find('#' + $listArea.attr('id') + " > div");
3996          for(let item of $list) {
3997            $listArea.append(item);
3998          }
3999          // ページング差換え
4000          let $pageing = $res.find('.paging_area');
4001          $pagingAuto.empty();
4002          $pagingAuto.append($pageing.children());
4003          prjutils.isPagingAutoLoading = false;
4004        }
4005    });
4006  },
4007
4008  // ランダムな文字列を生成する
4009  // @param length 生成する文字数
4010  createRandomString: function(length) {
4011    let result = '';
4012    let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
4013    let charactersLength = characters.length;
4014    for(let i = 0; i < length; i++) {
4015      result += characters.charAt(Math.floor(Math.random() * charactersLength));
4016    }
4017    return result;
4018  },
4019
4020  // デバイス識別子(ランダム値)を取得
4021  getDeviceKey: function(){
4022    let key = localStorage.getItem('push_device_key');
4023    if (!key) {
4024        key = prjutils.createRandomString(12);
4025        localStorage.setItem('push_device_key', key);
4026    }
4027    return key;
4028  },
4029
4030  // 重複しないIDを生成する
4031  getNewId: function() {
4032    let id = crypto.randomUUID();
4033    return id;
4034  },
4035
4036  /**
4037   * ファイル名から拡張子を取得する
4038   * @param {string} filename ファイル名
4039   * @returns {string} 拡張子、拡張子がない場合は空文字列 ''
4040   */
4041  getFileExtension: function(filename) {
4042    if (!filename) {
4043      return '';
4044    }
4045    let lastDotIndex = filename.lastIndexOf('.');
4046    if (lastDotIndex < 0 || filename.length === lastDotIndex + 1) {
4047      return ''; // 拡張子なし
4048    }
4049    // 拡張子を抽出
4050    let extension = filename.slice(lastDotIndex + 1);
4051    return extension;
4052  },
4053
4054  /**
4055   * ファイル名から拡張子を除いた部分を取得する
4056   * @param {string} filename 拡張子を含むファイル名
4057   * @returns {string} 拡張子を除いたファイル名
4058   */
4059  getFileName: function(filename) {
4060    if (!filename) {
4061      return '';
4062    }
4063    let lastDotIndex = filename.lastIndexOf('.');
4064    if (lastDotIndex <= 0) {
4065      return filename;
4066    }
4067    let res = filename.substring(0, lastDotIndex);
4068    return res;
4069  },
4070
4071  // ファイル名から動画であるかを判定する
4072  isMovieFilename: function(filename) {
4073    let extension = prjutils.getFileExtension(filename).toLowerCase();
4074    let movies = ['mov', 'mp4'];
4075
4076    let isMovie = movies.includes(extension);
4077    return isMovie;
4078  },
4079
4080  // 決済プロジェクトを完了する
4081  postProjectStatusClose: function(clubCd, projectType, projectId, confirmTitle, collbackFunc) {
4082    if(!confirmTitle) {
4083      confirmTitle = '集金受付を終了します。';
4084    }
4085    let postParam = new FormData();
4086    postParam.append('club_cd', clubCd);
4087    postParam.append('project_type', projectType);
4088    postParam.append('project_id', projectId);
4089    postParam.append('action', 'closePayProjectPost');
4090    confirm_msg_html(confirmTitle, function () {
4091      $.ajax({
4092        xhr: prjutils.ajaxXhr,
4093        type: 'POST',
4094        url: binds['ajaxurl'],
4095        data: postParam,
4096        contentType: false,
4097        processData: false,
4098        success: function(res){
4099          prjutils.loadingImgDel();
4100          if(res['success'] === true){
4101            success_msg(success_save, collbackFunc);
4102          } else if(res['error']){
4103            alert_msg_html(res['error'], null);
4104          } else {
4105            alert_msg(unknown_error, null);
4106          }
4107        }
4108      })
4109      .fail(prjutils.ajaxFail)
4110      .always(prjutils.loadingImgDel);
4111    });
4112  },
4113
4114  // 定期決済プロジェクトの振込依頼を作成する
4115  postTransferRequestContinuation: function(clubCd, projectType, projectId, confirmTitle, collbackFunc) {
4116    if(!confirmTitle) {
4117      confirmTitle = '振込依頼を作成します。';
4118    }
4119    let postParam = new FormData();
4120    postParam.append('club_cd', clubCd);
4121    postParam.append('project_type', projectType);
4122    postParam.append('project_id', projectId);
4123    postParam.append('action', 'requestContinuationProjectPost');
4124    confirm_msg(confirmTitle, function () {
4125      $.ajax({
4126        xhr: prjutils.ajaxXhr,
4127        type: 'POST',
4128        url: binds['ajaxurl'],
4129        data: postParam,
4130        contentType: false,
4131        processData: false,
4132        success: function(res){
4133          prjutils.loadingImgDel();
4134          if(res['success'] === true){
4135            let transferRequestManagementId = res['transfer_request_management_id'];
4136            if(collbackFunc) {
4137              collbackFunc(transferRequestManagementId);
4138            }
4139          } else if(res['error']){
4140            alert_msg_html(res['error'], null);
4141          } else {
4142            alert_msg(unknown_error, null);
4143          }
4144        }
4145      })
4146      .fail(prjutils.ajaxFail)
4147      .always(prjutils.loadingImgDel);
4148    });
4149  },
4150  
4151  // 決済プロジェクトの支払依頼に遷移する
4152  fowardTransferRequest: function(element, clubCd, trId, event) {
4153    if(event) {
4154      event.stopPropagation();
4155    }
4156    let $this = $(element);
4157    let $box = $this.closest('[data-transfer_request_id]');
4158    if($box.length == 0) {
4159      let $wrap = $this.closest('.project_wrap');
4160      $box = $wrap.find('[data-transfer_request_id]');
4161    }
4162    let id = $box.attr('data-transfer_request_id');
4163    if(trId) {
4164      id = trId;
4165    }
4166    //  画面遷移
4167    let url = "/admin/clubtransferrequest/?club_cd=" + clubCd + "&tr_id=" + id;
4168    window.open(url, "_self");
4169  },
4170
4171  // 決済プロジェクトの支払依頼に遷移する
4172  requestTransfer: function(element, clubCd, trId) {
4173    let $this = $(element);
4174    let $box = $this.closest('[data-transfer_request_id]');
4175    if($box.length == 0) {
4176      let $wrap = $this.closest('.project_wrap');
4177      $box = $wrap.find('[data-transfer_request_id]');
4178    }
4179    let id = $box.attr('data-transfer_request_id');
4180    if(trId) {
4181      id = trId;
4182    }
4183    // 金額チェック(500円未満は振込依頼不可)
4184    let dataAmount = $box.attr('data-amount');
4185    if (dataAmount !== undefined) {
4186      let amount = parseInt(dataAmount, 10);
4187      if (!isNaN(amount) && amount < 500) {
4188        alert_msg_html(transferrequest_under_500_error, null);
4189        return;
4190      }
4191    }
4192    // 確認ダイアログを表示
4193    confirm_msg_html("1件振込依頼します。\n\n※弊社からの振込処理は月2回です。\n1~2週目のご依頼 → 当月末\n3週目~月末のご依頼 → 翌月15日\n\n※振込日が休業日の場合、翌営業日に振込となります。", function(){
4194      // 一括振込を実行
4195      let postParam = new FormData();
4196      postParam.append('action', 'requestTransferPost');
4197      postParam.append('club_cd', clubCd);
4198      postParam.append('arr_transfer_id', [id]);
4199
4200      $.ajax({
4201        xhr: prjutils.ajaxXhr,
4202        type: 'POST',
4203        url: binds['ajaxurl'],
4204        data: postParam,
4205        contentType: false,
4206        processData: false,
4207        success: function(res){
4208          prjutils.loadingImgDel();
4209          if(res['success'] === true) {
4210            success_msg_html("振込依頼が完了しました。", function(){
4211              location.reload();
4212            });
4213          } else if(res['error']){
4214              alert_msg(res['error'], null);
4215          } else {
4216              alert_msg(unknown_error, null);
4217          }
4218        }
4219      })
4220      .fail(prjutils.ajaxFail)
4221      .always(prjutils.loadingImgDel);
4222    });
4223  },
4224  
4225  // ヒントを表示する
4226  setPopup: function($area) {
4227    $area.find('.popup_hint').on('mouseover', function(){
4228      let $this = $(this);
4229      let hintId = $this.attr('data-hint');
4230      let $hint = $('#' + hintId);
4231      $hint.show();
4232    });
4233    $area.find('.popup_hint').on('mouseleave', function(e) {
4234      let $this = $(this);
4235      let hintId = $this.attr('data-hint');
4236      let $hint = $('#' + hintId);
4237      $hint.hide();
4238    });
4239  }, 
4240  
4241  // Page管理 左サブメニュー表示 マウスオーバー制御
4242  popupSubMenu: function($popup) {
4243    let $submenu = $popup.closest('.submenu');
4244    let rect = $submenu[0].getBoundingClientRect();
4245    let viewHeight = window.innerHeight;
4246    let top = rect.top;
4247    let menuHeight = rect.height * $submenu.find('.subpopup > li').length;
4248    // サブメニュー内タイトル の高さを追加
4249    menuHeight += 30 * $submenu.find('.subpopup > .side_submenu_label').length;
4250    if(viewHeight < (top + menuHeight)) {
4251        top = viewHeight - menuHeight;
4252    }
4253    $popup.css('top', top + 'px');
4254    $('.subpopup:visible').hide();
4255    $popup.show();
4256    $('body').addClass('popup');
4257  },
4258
4259  // ニュース・限定公開 追加・編集ダイアログのボタン制御
4260  setNewsButtons: function($newsBox, $btnArea) {
4261    let arrShowButtons = [];
4262    // 一時保存ボタン
4263    arrShowButtons['save_temp'] = true;
4264    // 公開保存ボタン
4265    arrShowButtons['save_publish'] = true;
4266    // 削除ボタン
4267    arrShowButtons['delete'] = false;
4268    // 差し戻しボタン
4269    arrShowButtons['revert'] = false;
4270    // 保存(承認して公開)ボタン
4271    arrShowButtons['save_approval'] = false;
4272    // 保存(承認依頼)ボタン
4273    arrShowButtons['save_approvalRequest'] = false;
4274    
4275    // 新規追加
4276    arrShowButtons['request_edit'] = false;
4277    arrShowButtons['permit_edit'] = false;
4278    arrShowButtons['request_delete'] = false;
4279    arrShowButtons['permit_delete'] = false;
4280
4281    // 承認権限の有無
4282    if($('#isApprover').val() == "1") {
4283      // 承認権限がある場合
4284      // 新規ニュース作成の場合
4285      if (!$newsBox) {
4286        // 一時保存ボタンを表示
4287        arrShowButtons['save_temp'] = true;
4288      } else {
4289        // 既存ニュース編集の場合
4290        // 削除ボタンを表示
4291        arrShowButtons['delete'] = true;
4292        // 承認待ち・再承認待ちか否か
4293        if ($newsBox.find('.approval_tag, .re_approval_tag').length > 0 || $newsBox.attr('data-publish_type') === 'approval') {
4294          // 承認待ちの場合
4295          // 差し戻しボタンを表示
4296          arrShowButtons['revert'] = true;
4297          // 保存(承認して公開)ボタンを表示
4298          arrShowButtons['save_approval'] = true;
4299          // 公開保存ボタンを非表示
4300          arrShowButtons['save_publish'] = false;
4301        }
4302
4303        // 編集依頼が出ている場合、編集許可ボタンを表示
4304        if ($newsBox.attr('data-edit_request_flg') === '1') {
4305          arrShowButtons['permit_edit'] = true;
4306        }
4307        // 削除依頼が出ている場合、削除許可ボタンを表示
4308        if ($newsBox.attr('data-delete_request_flg') === '1') {
4309          arrShowButtons['permit_delete'] = true;
4310        }
4311      }
4312    } else {
4313      // 承認権限がない場合
4314      // 新規ニュース作成の場合
4315      if (!$newsBox) {
4316        // 新規作成
4317        arrShowButtons['save_approvalRequest'] = true;
4318        arrShowButtons['save_publish'] = false;
4319      } else {
4320        // 既存ニュース編集
4321        const isTemp = $newsBox.find('#NewsTemporaryFlg,.NewsTemporaryFlg').length > 0;
4322        const isRevert = $newsBox.find('.revert_tag').length > 0;
4323        const editPermit = $newsBox.attr('data-edit_permit_flg') === '1';
4324        const deletePermit = $newsBox.attr('data-delete_permit_flg') === '1';
4325        const editRequest = $newsBox.attr('data-edit_request_flg') === '1';
4326        const deleteRequest = $newsBox.attr('data-delete_request_flg') === '1';
4327        
4328        // 一時保存・差し戻し中・または編集許可がある場合は、編集・保存・承認依頼が可能
4329        if (isTemp || isRevert || editPermit) {
4330          arrShowButtons['save_temp'] = true;
4331          arrShowButtons['save_approvalRequest'] = true;
4332          arrShowButtons['save_publish'] = false;
4333        } else {
4334          // 公開中で編集許可がない場合
4335          arrShowButtons['save_temp'] = false;
4336          arrShowButtons['save_publish'] = false;
4337        }
4338
4339        // 削除許可がある場合は、削除ボタンを表示
4340        if (deletePermit) {
4341          arrShowButtons['delete'] = true;
4342        } else {
4343          arrShowButtons['delete'] = false;
4344        }
4345      }
4346    }
4347    let keys = Object.keys(arrShowButtons);
4348    for(let btnClass of keys) {
4349      let isShow = arrShowButtons[btnClass];
4350      if(isShow){
4351        $btnArea.find('.' + btnClass).show();
4352      } else {
4353        $btnArea.find('.' + btnClass).hide();
4354      }
4355    }
4356  },
4357
4358  // IFrameでダイアログを表示する
4359  // 例:埋め込み形式URLの地図
4360  openIFrameDialog: function(iframeUrl) {
4361    let $dialog = $('#iframeDialog');
4362    $dialog.find('iframe').attr('src', iframeUrl);
4363    prjutils.showDialog($dialog);
4364  },
4365
4366  // ギャラリー追加・編集ダイアログのボタン制御
4367  setGalleryButtons: function($galleryBox, $btnArea) {
4368    let arrShowButtons = [];
4369    // 一時保存ボタン
4370    arrShowButtons['save_temp'] = true;
4371    // 公開保存ボタン
4372    arrShowButtons['save_publish'] = true;
4373    // 削除ボタン
4374    arrShowButtons['delete'] = false;
4375    // 差し戻しボタン
4376    arrShowButtons['revert'] = false;
4377    // 保存(承認して公開)ボタン
4378    arrShowButtons['save_approval'] = false;
4379    // 保存(承認依頼)ボタン
4380    arrShowButtons['save_approvalRequest'] = false;
4381    // 承認権限の有無
4382    if($('#isApprover').val() == "1") {
4383      // 承認権限がある場合
4384      // 新規ギャラリー作成の場合
4385      if (!$galleryBox) {
4386        // 一時保存ボタンを表示
4387        arrShowButtons['save_temp'] = true;
4388      } else {
4389        // 既存ギャラリー編集の場合
4390        // 削除ボタンを表示
4391        arrShowButtons['delete'] = true;
4392        // 承認待ちか否か
4393        if ($galleryBox.find('.approval_tag').length > 0) {
4394          // 承認待ちの場合
4395          // 差し戻しボタンを表示
4396          arrShowButtons['revert'] = true;
4397          // 保存(承認して公開)ボタンを表示
4398          arrShowButtons['save_approval'] = true;
4399          // 公開保存ボタンを非表示
4400          arrShowButtons['save_publish'] = false;
4401        }
4402      }
4403    } else {
4404      // 承認権限がない場合
4405      // 新規ギャラリー作成の場合
4406      if (!$galleryBox) {
4407      } else {
4408        // 既存ギャラリー編集の場合、削除ボタンを表示
4409        arrShowButtons['delete'] = true;
4410      }
4411      // 新規作成や一時保存か否か
4412      if ($galleryBox === null || $galleryBox.find('#GalleryTemporaryFlg,.GalleryTemporaryFlg').length > 0) {
4413        // 新規作成や一時保存の場合
4414        // 保存(承認依頼)ボタンを表示
4415        arrShowButtons['save_approvalRequest'] = true;
4416        // 公開保存ボタンを非表示
4417        arrShowButtons['save_publish'] = false;
4418        // }
4419      } else {
4420        // 新規作成や一時保存ではない場合(公開中)
4421        // 一時保存ボタンを非表示(※実際表示しているが必要な記述)
4422        arrShowButtons['save_temp'] = false;
4423        // 公開保存ボタンを非表示
4424        arrShowButtons['save_publish'] = false;
4425        // 削除ボタンを非表示
4426        arrShowButtons['delete'] = false;
4427      }
4428    }
4429
4430    let keys = Object.keys(arrShowButtons);
4431    for(let btnClass of keys) {
4432      let isShow = arrShowButtons[btnClass];
4433      if(isShow){
4434        $btnArea.find('.' + btnClass).show();
4435      } else {
4436        $btnArea.find('.' + btnClass).hide();
4437      }
4438    }
4439  },
4440
4441  // ツールチップ編集 開始
4442  setModeSuperTooltipOn: function() {
4443    // スーパーメニュー切替え
4444    $('body').addClass('common-tooltip-editmode');
4445
4446    // この画面のツールチップを取得
4447    prjutils.loadTooltips();
4448  },
4449
4450  // ツールチップ編集 終了
4451  setModeSuperTooltipOff: function() {
4452    // スーパーメニュー切替え
4453    $('body').removeClass('common-tooltip-editmode');
4454    prjutils.loadingImg();
4455    location.reload();
4456  },
4457
4458  // この画面のツールチップ設置可能位置に編集アイコンを設置する
4459  // スーパーメニューで開始するまでは非表示
4460  addTooltipPositions: function() {
4461    // h3 タイトル
4462    let $item = $('h3.the_title');
4463    let $tipCd = 'h3_the_title';
4464    prjutils.addTooltipTemplate($item, $tipCd);
4465    
4466    // div id付
4467    // button id付
4468    // label for付 
4469    $('div[id],button[id],input[type="button"][id],input[type="submit"][id],label[for]').each(function(){
4470      let $item = $(this);
4471      // 除外判定
4472      if($item.closest('#superTooltipEditDialog').length > 0) {
4473        // ツールチップ編集ダイアログ内は除外
4474        return true; // continue
4475      }
4476      $tipCd = prjutils.createTooltipCd($item);
4477      prjutils.addTooltipTemplate($item, $tipCd);
4478    });
4479  },
4480
4481  // ツールチップ編集アイコンのコードを生成する
4482  // idのある項目のみ対応可能
4483  createTooltipCd: function($item) {
4484    let isSide = $item.closest('#side').length > 0;
4485    let isMenu = $item.closest('#header').length > 0;
4486    let id = $item.attr('id');
4487    if(!id) {
4488      id = $item.attr('for');
4489    }
4490    if(isSide || isMenu) {
4491      id = 'sidebar-' + id;
4492    }
4493    return id;
4494  },
4495
4496  // 指定項目にツールチップ編集アイコンを設置する
4497  addTooltipTemplate: function($position, $tipCd) {
4498    let $template = $('#superTooltip_template');
4499    let $newWrap = $template.find('.common-tooltip-wrap').clone();
4500    $newWrap.attr('data-tooltip_cd', $tipCd);
4501    if($position.is('button,input[type="button"],input[type="submit"]')) {
4502      $position.after($newWrap);
4503    } else {
4504      $position.append($newWrap);
4505    }
4506  },
4507
4508  // この画面のツールチップを読み込む
4509  loadTooltips: function() {
4510    let currentPath = location.pathname;
4511    let postParam = new FormData();
4512    postParam.append('screen_path', currentPath);
4513    postParam.append('action', 'loadSuperTooltipPost');
4514    $.ajax({
4515      // xhr: prjutils.ajaxXhr,
4516      type: 'POST',
4517      url: binds['ajaxurl'],
4518      data: postParam,
4519      contentType : false,
4520      processData : false,
4521      success: function(res) {
4522        if(res['success'] === true) {
4523          let $targets = $('div.common-tooltip-wrap[data-tooltip_cd]');
4524          for(let tip of res['list']) {
4525            prjutils.setTooltip(tip, $targets);
4526          }
4527          prjutils.setAllTooltipEvent();
4528        }
4529      }
4530    });
4531  },
4532
4533  // この画面のツールチップ1つ設定する
4534  setTooltip: function(tip, $targets) {
4535    let tooltipCd = tip['tooltip_cd'];
4536    let $wrap = $targets.filter('[data-tooltip_cd="' + tooltipCd + '"]');
4537    
4538    // アイコン設置
4539    let $iconDiv = $wrap.find('.common-tooltip-icon');
4540    let iconTopPx = tip['icon_top_px'] ? tip['icon_top_px'] : 0;
4541    let iconLeftTPx = tip['icon_left_px'] ? tip['icon_left_px'] : 0;
4542    $iconDiv.css('top', iconTopPx + 'px');
4543    $iconDiv.css('left', iconLeftTPx + 'px');
4544
4545    let iconTextHtml = tip['icon_text'];
4546    $iconDiv.html(iconTextHtml);
4547    
4548    // 内容設置
4549    let $contentDiv = $wrap.find('.common-tooltip-content');
4550    let contentsTopPx = tip['contents_top_px'] ? tip['contents_top_px'] : 0;
4551    let contentsLeftPx = tip['contents_left_px'] ? tip['contents_left_px'] : 0;
4552    $contentDiv.css('top', contentsTopPx + 'px');
4553    $contentDiv.css('left', contentsLeftPx + 'px');
4554
4555    // 吹出し生成
4556    if(tip['border_enabled_flg'] == "1") {
4557      let borderColor = tip['border_color'];
4558      let cssTail = "border_tail_" + tip['border_tail_type'];
4559
4560      $contentDiv.css('background-color', '#ffffff');
4561      $contentDiv.css('padding', '0 4px');
4562      $contentDiv.css('border-radius', '5px');
4563      $contentDiv.css('border', '1px solid ' + borderColor);
4564      $contentDiv.css('--common-tooltip-color', borderColor); // 吹出しのしっぽ
4565
4566      $contentDiv.addClass(cssTail);
4567    }
4568
4569    let contentsHtml = tip['contents'];
4570    $contentDiv.html(contentsHtml);
4571
4572    // JSON埋め込み
4573    let $jsonDiv = $wrap.find('.common-tooltip-json');
4574    let tooltipJson = JSON.stringify(tip);
4575    $jsonDiv.text(tooltipJson);
4576  },
4577
4578  // この画面内の全ツールチップにイベントを設置する
4579  setAllTooltipEvent: function() {
4580    let hasTip = false;
4581    let popupList = [];
4582    let $wrapList = $('.common-tooltip-wrap');
4583    $wrapList.each(function(){
4584      let $wrap = $(this);
4585      let $icon = $wrap.find('.common-tooltip-icon');
4586      let $content = $wrap.find('.common-tooltip-content');
4587      if($content.text().trim() === "") {
4588        return true; // continue
4589      }
4590      hasTip = true;
4591      popupList.push($wrap);
4592      $icon.on('mouseenter', function(){
4593        prjutils.onTooltip($icon, $content);
4594      });
4595    });
4596
4597    if(hasTip) {
4598      $(document).on('mousemove', function(e) {
4599        let $target = $(e.target);
4600        let $wrap = $target.closest('.common-tooltip-wrap');
4601        if($wrap.length > 0) {
4602          return;
4603        }
4604        prjutils.offTooltip(popupList);
4605      });
4606    }
4607  },
4608
4609  // ツールチップの内容を表示する
4610  onTooltip: function($icon, $content) {
4611    if($('body').is('.common-tooltip-editmode')) {
4612      return;
4613    }
4614    $content.addClass('common-tooltip-open');  
4615  },
4616  // ツールチップの内容を閉じる
4617  offTooltip: function(wrapList) {
4618    for(let i = 0; i < wrapList.length; i++){
4619      $wrap = wrapList[i];
4620      let $content = $wrap.find('.common-tooltip-content');
4621      $content.removeClass('common-tooltip-open');
4622    }
4623  },
4624
4625  // 指定のツールチップを編集ダイアログに設定する
4626  setTooltipDialog: function($tooltip) {
4627    let $dialog = $('#superTooltipEditDialog');
4628    prjutils.clearFormValues($dialog);
4629 
4630    // 選択したツールチップ情報
4631    let $jsonDiv = $tooltip.find('.common-tooltip-json');
4632    let json = $jsonDiv.text();
4633    if(!json) {
4634      json = '{}';
4635    }
4636    let tip = JSON.parse(json);
4637    let tooltipCd = $tooltip.attr('data-tooltip_cd');
4638
4639    // ダイアログに初期値として設定
4640    $('#superTooltip_tooltip_cd').val(tooltipCd);
4641    if(tip['id']) {
4642      $('#superTooltip_tooltip_id').val(tip['id']);
4643      // アイコン
4644      $('#superTooltip_icon_text').val(tip['icon_text']);
4645      $('#superTooltip_icon_top_px').val(tip['icon_top_px']);
4646      $('#superTooltip_icon_left_px').val(tip['icon_left_px']);
4647      // 内容
4648      $('#superTooltip_contents').val(tip['contents']);
4649      $('#superTooltip_contents_top_px').val(tip['contents_top_px']);
4650      $('#superTooltip_contents_left_px').val(tip['contents_left_px']);
4651      // 吹出し
4652      $('#superTooltip_border_enabled_flg').prop('checked', tip['border_enabled_flg'] == '1');
4653      $('#superTooltip_border_color').val(tip['border_color']);
4654      $('#superTooltip_border_tail_type').val(tip['border_tail_type']);
4655      
4656      // 確認モード
4657      $('#superTooltip_super_only_flg').prop('checked', tip['super_only_flg'] == '1');
4658    }
4659  },
4660
4661  // ツールチップ編集ダイアログを開く
4662  openTooltipDialog: function(evt, element) {
4663    let $dialog = $('#superTooltipEditDialog');
4664    evt.stopPropagation();
4665    let $this = $(element);
4666    let $tooltip = $this.closest('[data-tooltip_cd]');
4667
4668    // 既存値設定
4669    prjutils.setTooltipDialog($tooltip);
4670
4671    let customEdit = [];
4672    customEdit['default_height'] = 100;
4673
4674    prjutils.setupHtmlEditor($('#superTooltip_icon_text'), customEdit);
4675    prjutils.setupHtmlEditor($('#superTooltip_contents'), customEdit);
4676    prjutils.showDialog($dialog);
4677    clearValidationMessage($dialog);
4678  },
4679
4680  // ツールチップダイアログ内容を保存する
4681  saveSuperTooltip: function() {
4682    save_confirm_msg(write_caution, function () {
4683      let currentPath = location.pathname;
4684      let $form = $('#superTooltipForm');
4685      let postParam = new FormData($form.get(0));
4686      postParam.append('screen_path', currentPath);
4687      postParam.append('action', 'saveSuperTooltipPost');
4688      $.ajax({
4689        xhr: prjutils.ajaxXhr,
4690        type: 'POST',
4691        url: binds['ajaxurl'],
4692        data: postParam,
4693        contentType : false,
4694        processData : false,
4695        success: function(res) {
4696          if(res['success'] === true) {
4697            prjutils.loadTooltips();
4698            success_msg(success_save, function() {
4699              let $dialog = $('#superTooltipEditDialog');
4700              prjutils.hideDialog($dialog);
4701            });
4702          } else if(res['error']){
4703              alert_msg(res['error'], null);
4704          } else {
4705              alert_msg(unknown_error, null);
4706          }
4707        }
4708      })
4709      .fail(prjutils.ajaxFail)
4710      .always(prjutils.loadingImgDel);
4711    });
4712  },
4713
4714  // ツールチップを削除する
4715  deleteSuperTooltip: function() {
4716    delete_confirm_msg(delete_caution, function () {
4717      // let currentPath = location.pathname;
4718      let $form = $('#superTooltipForm');
4719      let tooltipId = $form.find('#superTooltip_tooltip_id').val();
4720      let postParam = new FormData();
4721
4722      postParam.append('tooltip_id', tooltipId);
4723      postParam.append('action', 'deleteSuperTooltipPost');
4724      $.ajax({
4725        xhr: prjutils.ajaxXhr,
4726        type: 'POST',
4727        url: binds['ajaxurl'],
4728        data: postParam,
4729        contentType : false,
4730        processData : false,
4731        success: function(res) {
4732          if(res['success'] === true) {
4733            prjutils.loadTooltips();
4734            success_msg(success_delete, function() {
4735              let $dialog = $('#superTooltipEditDialog');
4736              prjutils.hideDialog($dialog);
4737            });
4738          } else if(res['error']){
4739              alert_msg(res['error'], null);
4740          } else {
4741              alert_msg(unknown_error, null);
4742          }
4743        }
4744      })
4745      .fail(prjutils.ajaxFail)
4746      .always(prjutils.loadingImgDel);
4747    });
4748  }, // deleteSuperTooltip
4749
4750  // 施設備品 ReadOnlyダイアログを開く
4751  openFacilityReadOnlyDialog: function(element) {
4752    let $dialog = $('#dialogFacilityReadOnly');
4753    if(element) {
4754      let $this = $(element);
4755      let $facility = $this.closest('.facility_body');
4756      let json = $facility.find('.facility_json').text();
4757      let map = JSON.parse(json);
4758      prjutils.setFacilityReadOnlyDialog(map);
4759    }
4760    prjutils.showDialog($dialog);
4761  },
4762  // 施設備品 ReadOnlyダイアログ内容を設定する
4763  setFacilityReadOnlyDialog: function(map) {
4764    $('#dialogFacilityReadOnlyTitle').text(map['title']);
4765    // 画像
4766    let $img = $('#dialogFacilityReadOnly_image');
4767    $img.addClass('none');
4768    if(map['image_json']) {
4769      let image = JSON.parse(map['image_json']);
4770      $img.attr('src', binds['contenturl'] + image['path']);
4771      $img.removeClass('none');
4772    }
4773    // 予約時の注意事項
4774    let $bookingDiv = $('#dialogFacilityReadOnly_bookingNoteDiv');
4775    let booking = map['booking_note'];
4776    if(booking) {
4777      $bookingDiv.removeClass('none');
4778    } else {
4779      $bookingDiv.addClass('none');
4780    }
4781    $('#dialogFacilityReadOnly_bookingNote').text(booking);
4782
4783    // 使用時の注意事項
4784    let $usageNoteDiv = $('#dialogFacilityReadOnly_usageNoteDiv');
4785    let usage = map['usage_note'];
4786    if(usage) {
4787      $usageNoteDiv.removeClass('none');
4788    } else {
4789      $usageNoteDiv.addClass('none');
4790    }
4791    $('#dialogFacilityReadOnly_usageNote').text(usage);
4792  },
4793
4794  // 施設一覧を表示する
4795  setFacilityTable: function(res) {
4796    let dateList = res['dates'];
4797    let facilityList = res['facilities'];
4798    let reservationMap = res['reservations'];
4799    let $facilityDiv = $('#facilityDiv');
4800    let isForSchool = $facilityDiv.closest('.school_reservations').length > 0;
4801    let today = prjutils.getToday();
4802
4803    // 表示済項目をクリア
4804    $facilityDiv.find('.facility_head_date:not(.template)').remove();
4805    $facilityDiv.find('#facilityTableBody > tr:not(.template)').remove();
4806    $('#facilityNotFound').addClass('none');
4807
4808    // ヘッダ日付行を生成
4809    let $trHead = $facilityDiv.find('.facility_row_head');
4810    let $templeteHead = $facilityDiv.find('.facility_head_date.template');
4811    for(let dateYmd of Object.keys(dateList)) {
4812      let date = dateList[dateYmd];
4813      let inputDate = date['input_date'];
4814      let $newHead = $templeteHead.clone();
4815      $newHead.removeClass('template');
4816      $newHead.text(date['display_short_day']);
4817      let cssHoliday = date['holiday_flg'] == '1' ?  'holiday' : '';
4818      $newHead.addClass(cssHoliday);
4819      $newHead.addClass('day' + date['day_number']);
4820      let cssToday = date['input_date'] == today ? 'today' : '';
4821      $newHead.addClass(cssToday);
4822      // today
4823      $trHead.append($newHead);
4824    }
4825
4826    if(facilityList.length <= 0) {
4827      $('#facilityNotFound').removeClass('none');
4828    } else {
4829      $('#facilityTable').removeClass('none');
4830    }
4831
4832    let $tbody = $('#facilityTableBody');
4833    $tbody.on('click', '.facility_reservation_plus', function(){
4834      prjutils.openFacilityReservationDialog(this);
4835    });
4836
4837    // 施設備品 行ループ
4838    let $trTemplate = $tbody.find('tr.template');
4839    for(let facility of facilityList) {
4840      let $newTr = $trTemplate.clone();
4841      $newTr.removeClass('template');
4842      $newTr.attr('data-facility_id', facility['id']);
4843      let $tdFacility = $newTr.find('.facility_body');
4844      let $facilityTitle = $tdFacility.find('.facility_title');
4845      let $facilityMonthly = $tdFacility.find('.facility_monthly');
4846      if(isForSchool) {
4847        // 学校(管理)側
4848        $tdFacility.find('.facility_title,.facility_image').on('click', function(){
4849          adminSchoolFacility.openFacilityDialog(this);
4850        });
4851        $tdFacility.find('.facility_title .facility_edit').removeClass('none_imp');
4852        
4853      } else {
4854        // 部活(予約)側
4855        $tdFacility.find('.facility_title,.facility_image').on('click', function(){
4856          prjutils.openFacilityReadOnlyDialog(this);
4857        });
4858      }
4859
4860      $facilityMonthly.on('click', function(){
4861        prjutils.openFacilityMonthlyDialog(this);
4862      });
4863
4864      let json = JSON.stringify(facility);
4865      $tdFacility.find('.facility_json').text(json);
4866
4867      $facilityTitle.find('.text').text(facility['title']);
4868      if(facility['image_json']) {
4869        let image = JSON.parse(facility['image_json']);
4870        let $img = $tdFacility.find('.facility_image');
4871        $img.attr('src', binds['contenturl'] + image['path']);
4872        $img.removeClass('none');
4873      }
4874
4875      // 施設備品 日別 ループ
4876      let $tdTemplate = $newTr.find('.facility_reservation');
4877      for(let dateYmd of Object.keys(dateList)) {
4878        let dateMap = dateList[dateYmd];
4879        $newTd = $tdTemplate.clone();
4880        $newTd.attr('data-ymd', dateMap['input_date']);
4881        let cssToday = dateMap['input_date'] == today ? 'today' : '';
4882        $newTd.addClass(cssToday);
4883
4884        let $plus = $newTd.find('.facility_reservation_plus');
4885        let reservationKey = dateYmd + '_'  + facility['id'];
4886        if(reservationMap[reservationKey]) {
4887          for(reservation of reservationMap[reservationKey]) {
4888            let $reservation = prjutils.getFacilityReservationHtml(reservation, dateMap['input_date']);
4889            $plus.before($reservation);
4890          }
4891        }
4892
4893        $newTr.append($newTd);
4894      }
4895      $tdTemplate.remove();
4896      $tbody.append($newTr);
4897    }
4898    
4899  }, // setFacilityTable
4900
4901  // 施設備品(月別)一覧 ダイアログ内容をクリアする
4902  clearFacilityMonthlyDialog: function() {
4903    let $tableBody = $('#facilityMonthlyTableBody');
4904    let $trTemplate = $tableBody.find('tr.template');
4905    $tableBody.find('tr:not(.template)').remove();
4906    // 最低の横幅を確保して初期表示するため
4907    $newTr = $trTemplate.clone();
4908    $newTr.removeClass('template');
4909    $tableBody.append($newTr);
4910  }, // clearFacilityMonthlyDialog
4911  
4912  // 施設備品(月別)一覧 ダイアログを開く
4913  openFacilityMonthlyDialog: function(element) {
4914    let $dialog = $('#facilityMonthlyDialog');
4915    
4916    prjutils.clearFacilityMonthlyDialog();
4917    prjutils.setMonthSelection($dialog, {
4918      'after_prev': prjutils.searchMonthlyFacilities,
4919      'after_next': prjutils.searchMonthlyFacilities,
4920    });
4921
4922    let $this = $(element);
4923    let $facility = $this.closest('.facility_body');
4924    let json = $facility.find('.facility_json').text();
4925    if(json) {
4926      let map = JSON.parse(json);
4927      prjutils.setFacilityMonthlyDialog(map);
4928    }
4929    // 初期月設定
4930    let ymd = $('#selectedFacilityWeek').val();
4931    let dateYmd = new Date(ymd);
4932    let ym = prjutils.toYM(dateYmd);
4933    $('#selectedFacilityMonth').val(ym);
4934    prjutils.loadMonthSelection($('#facilityMonth'));
4935
4936    prjutils.showDialog($dialog);
4937    prjutils.searchMonthlyFacilities();
4938  }, // openFacilityMonthlyDialog
4939
4940  // 施設備品(月別)一覧 ダイアログに表示対象を設定する
4941  setFacilityMonthlyDialog: function(facility) {
4942    let $dialog = $('#facilityMonthlyDialog');
4943
4944    $('#facilityMonthlyDialog_facilityId').val(facility['id']);
4945
4946    $('#facilityMonthlyTitle').text(facility['title']);
4947    
4948    facility['image_json'];
4949    if(facility['image_json']) {
4950      let image = JSON.parse(facility['image_json']);
4951      let $img = $dialog.find('.dialog_header_image');
4952      $img.attr('src', binds['contenturl'] + image['path']);
4953      $dialog.find('.dialog_header_image_div').removeClass('none');
4954    } else {
4955      $dialog.find('.dialog_header_image_div').addClass('none');
4956    }
4957  },
4958
4959  // 施設設備(月別)一覧を表示
4960  searchMonthlyFacilities: function() {
4961    let searchMonth = $('#selectedFacilityMonth').val();
4962    let searchMonthLabel = $('#searchedMonthLabel').text();
4963    let $clubCd = $('#club_cd');
4964    let facilityId = $('#facilityMonthlyDialog_facilityId').val();
4965    let postParam = new FormData();
4966    postParam.append('action', 'searchSchoolFacilitiesPost');
4967    if($clubCd.length > 0) {
4968      postParam.append('club_cd', $clubCd.val());
4969    }
4970    postParam.append('facility_id', facilityId);
4971    postParam.append('month', searchMonth);
4972    if (!searchMonth) {
4973      return;
4974    }
4975    
4976    $('#searchedFacilityMonthLabel').text("検索中");
4977    $.ajax({
4978      type: 'POST',
4979      url: binds['ajaxurl'],
4980      data: postParam,
4981      contentType: false,
4982      processData: false,
4983      success: function(res){
4984        if(res['success'] === true){
4985          $('#searchedFacilityMonth').val(searchMonth);
4986          $('#searchedFacilityMonthLabel').text(searchMonthLabel);
4987          prjutils.setFacilityMonthlyTable(res);
4988        } else if(res['error']){
4989          alert_msg(res['error'], null);
4990        } else {
4991          alert_msg(unknown_error, null);
4992        }
4993      }
4994    })
4995    .fail(prjutils.ajaxFail)
4996    .always(prjutils.loadingImgDel);
4997  },
4998
4999  // 施設備品予約の一カ月分を表示する
5000  setFacilityMonthlyTable: function(res) {
5001    let dateList = res['dates'];
5002    let facilityList = res['facilities'];
5003    let reservationMap = res['reservations'];
5004    let $facilityDiv = $('#facilityDiv');
5005    let isForSchool = $facilityDiv.closest('.school_reservations').length > 0;
5006    let today = prjutils.getToday();
5007    let $tdList = [];
5008    let facilityId = '';
5009    if(facilityList.length > 0) {
5010      facilityId = facilityList[0]['id'];
5011    }
5012
5013    let $tableBody = $('#facilityMonthlyTableBody');
5014    let $trTemplate = $tableBody.find('tr.template');
5015    // 表示済項目をクリア
5016    $tableBody.find('tr:not(.template)').remove();
5017
5018    // 日付ループ
5019    let lastDayNumber = 8;
5020    let $tr = null;
5021    for(let dateYmd of Object.keys(dateList)) {
5022      let date = dateList[dateYmd];
5023      let reservationKey = dateYmd + '_' + facilityId;
5024      let dayNumber = date['day_number'];
5025      if(dayNumber <= lastDayNumber) {
5026        lastDayNumber = dayNumber;
5027        $tr = $trTemplate.clone();
5028        $tr.removeClass('template');
5029        $tableBody.append($tr);
5030      }
5031      let $td = $tr.find('td.day' + dayNumber);
5032      $td.attr('data-input_date', date['input_date']);
5033      let cssHoliday = date['holiday_flg'] == '1' ?  'holiday' : '';
5034      $td.addClass(cssHoliday);
5035      let cssToday = date['input_date'] == today ? 'today' : '';
5036      $td.addClass(cssToday);
5037      $tdList[reservationKey] = $td;
5038      let $divDate = $('<div class="facility_date">' + date['display_short_day'] + '</div>');
5039      $td.append($divDate);
5040    }
5041
5042    // 予約ループ
5043    for(let mapKey of Object.keys(reservationMap)) {
5044      let $td = $tdList[mapKey];
5045      let inputDate = $td.attr('data-input_date');
5046      for(reservation of reservationMap[mapKey]) {
5047        let $reservation = prjutils.getFacilityReservationHtml(reservation, inputDate);
5048        $td.append($reservation);
5049      }
5050    }
5051
5052    let $tbody = $('#facilityTableBody');
5053    if(isForSchool) {
5054      // 学校(管理)側    
5055    } else {
5056      // 部活(予約)側
5057      $tbody.on('click', '.facility_reservation_plus', function(){
5058        prjutils.openFacilityReservationDialog(this);
5059      });
5060    }
5061  }, // setFacilityMonthlyTable
5062  
5063  // 1つの施設備品予約の表示項目を取得する
5064  getFacilityReservationHtml: function(reservation, inputDate) {
5065    let isSchoolReservations = $('#content.school_reservations').length > 0;
5066    let isClubReservationAdmin = $('#content.admin').length > 0;
5067    let isMonthly = $('#facilityMonthlyDialog:visible').length > 0;
5068    let isThisClub = reservation['this_club_flg'] == '1';
5069    let isEditable = isSchoolReservations || isThisClub;
5070    // 月表示ではすべて参照のみ
5071    if(isMonthly) {
5072      isEditable = false;
5073    }
5074    // 部活施設は予約管理者が編集可能、学校施設は画面を開ける全ユーザが編集可能
5075    if(!isClubReservationAdmin && !isSchoolReservations) {
5076      isEditable = false;
5077    }
5078    let html = '';
5079    html += '<div class="facility_reservation_div">';
5080    // 埋め込みjson
5081    html += '<div class="facility_reservation_json none"></div>';
5082    // バッジ
5083    html += '<div class="facility_badge" style="background-color:' + reservation['badge_color'] + ';"></div>';
5084    
5085    // 時刻
5086    let startDateTime = reservation['use_start_datetime'].split(' ');
5087    let startTime = '0:00';
5088    if(startDateTime[0] == inputDate) {
5089      startTime = prjutils.formatTimeToHmm(startDateTime[1]);
5090    }
5091    let endDateTime = reservation['use_end_datetime'].split(' ');
5092    let endTime = '24:00';
5093    if(endDateTime[0] == inputDate) {
5094      endTime = prjutils.formatTimeToHmm(endDateTime[1]);
5095    }
5096
5097    let onclick = '';
5098    if(isEditable) {
5099      onclick = ' onclick="prjutils.openFacilityReservationDialog(this)"';
5100    } else {
5101      onclick = ' onclick="prjutils.openFacilityReservationReadOnlyDialog(this)"';
5102    }
5103
5104    html += '<div class="facility_time"' +  onclick + '>' + startTime + '-' + endTime + '</div>';    
5105    
5106    if(isEditable) {
5107      // 編集アイコン
5108      html += '<div class="facility_edit icon-modify icon-very-xsmall-imp" ' + onclick + '></div>';
5109    }
5110
5111    // 部活名
5112    html += '<div class="facility_club_name" ' + onclick + '></div>';
5113
5114    // 予定名
5115    html += '<div class="facility_reserve_title" ' + onclick + '></div>';
5116
5117    html += '</div>';
5118    let $html = $(html);
5119
5120    // jQueryで埋め込み
5121    let json = JSON.stringify(reservation);
5122    if(isThisClub) {
5123      $html.addClass('this_club');
5124    }
5125    $html.find('.facility_club_name').text(reservation['club_name']);
5126    $html.find('.facility_club_name').attr('title', reservation['club_name']);
5127    $html.find('.facility_reserve_title').text(reservation['reserve_title']);
5128    $html.find('.facility_reserve_title').attr('title', reservation['reserve_title']);
5129    $html.find('.facility_reservation_json').text(json);
5130
5131    return $html;
5132  }, // getFacilityReservationHtml
5133
5134  // 画像選択ダイアログで手動アップロード選択時に起動するボタン
5135  imageSelectionUploadButton: null,
5136  imageSelectionProperties: [],
5137  imageSelectionSelectedFunc: null,
5138
5139  // 画像選択ダイアログを開く
5140  // imageSelectionType {int} 共通画像種別
5141  openImageSelectionDialog: function(element, imageSelectionType, postName, beforeFunc) {
5142    // 本来のアップロードボタン
5143    let $openButton = $(element);
5144    prjutils.imageSelectionUploadButton = $openButton.prev('input,button');
5145    prjutils.imageSelectionProperties['post_name'] = postName;
5146    prjutils.imageSelectionProperties['before_func'] = beforeFunc;
5147    prjutils.imageSelectionSelectedFunc = prjutils.selectedImageSelectionSingle;
5148
5149    let $dialog = $('#commonImageSelectionDialog');
5150    // タイトル表示
5151    let $dalogTitle = $dialog.find('.dialog_title');
5152    $dalogTitle.find('.selection_title').addClass('none');
5153    $dalogTitle.find('[data-type="' + imageSelectionType + '"]').removeClass('none');
5154
5155    $('#commonImageSelection_btnApply').hide();
5156    $('#commonImageSelection_btnClear').hide();
5157
5158    // 画像読み込み
5159    prjutils.loadImageSelection(imageSelectionType);
5160
5161    prjutils.showDialog($dialog);
5162  }, // openImageSelectionDialog
5163
5164  // 画像選択ダイアログを開く
5165  // imageSelectionType {int} 共通画像種別
5166  openImageSelectionAgentDialog: function(element, imageSelectionType, postName, beforeFunc) {
5167    // 本来のアップロードボタン
5168    let $openButton = $(element);
5169    prjutils.imageSelectionUploadButton = $openButton.prev('input,button');
5170    prjutils.imageSelectionProperties['post_name'] = postName;
5171    prjutils.imageSelectionProperties['before_func'] = beforeFunc;
5172    prjutils.imageSelectionSelectedFunc = prjutils.selectedImageSelectionSingle;
5173
5174    let $dialog = $('#commonImageSelectionAgentDialog');
5175    // タイトル表示
5176    let $dalogTitle = $dialog.find('.dialog_title');
5177    $dalogTitle.find('.selection_title').addClass('none');
5178    $dalogTitle.find('[data-type="' + imageSelectionType + '"]').removeClass('none');
5179
5180    // 画像読み込み
5181    prjutils.loadImageSelectionAgent(imageSelectionType);
5182
5183    agentCommon.openFullDialog($dialog);
5184  }, // openImageSelectionAgentDialog
5185
5186  /** 画像選択ダイアログを開く「選択結果を表の1行として追加するケース」
5187   * @param {element} element  イベント発生元(ボタン)
5188   * @param {int} imageSelectionType  共通画像種別
5189   * @param {string}
5189 tbodyId 追加先のテーブルBODYタグID
5190   * @param {string} remarks 備考配列(同じインデックスに表示)
5191   * @param {int} limit 上限
5192   * @param {function} callBackFunc 共通画像選択後の呼び出し処理
5193   */
5194  openImageSelectionDialogTable: function(element, imageSelectionType, tbodyId, remarks, limit, callBackFunc) {
5195    // 枚数チェック
5196    let $tbody = $('#' + tbodyId);
5197    if (limit && limit <= $tbody.find('tr:visible').length) {
5198        alert("画像のアップロード上限は" + limit + "枚です");
5199        return false;
5200    }
5201
5202    // 本来のアップロードボタン
5203    let $openButton = $(element);
5204    prjutils.imageSelectionUploadButton = $openButton.prev('input,button');
5205    prjutils.imageSelectionProperties['tbody_id'] = tbodyId;
5206    prjutils.imageSelectionProperties['remarks'] = remarks;
5207    prjutils.imageSelectionProperties['callback_func'] = callBackFunc;
5208    prjutils.imageSelectionSelectedFunc = prjutils.selectedImageSelectionTable;
5209
5210    let $dialog = $('#commonImageSelectionDialog');
5211    // タイトル表示
5212    let $dalogTitle = $dialog.find('.dialog_title');
5213    $dalogTitle.find('.selection_title').addClass('none');
5214    $dalogTitle.find('[data-type="' + imageSelectionType + '"]').removeClass('none');
5215
5216    $('#commonImageSelection_btnApply').hide();
5217    $('#commonImageSelection_btnClear').hide();
5218
5219    // 画像読み込み
5220    prjutils.loadImageSelection(imageSelectionType);
5221
5222    prjutils.showDialog($dialog);
5223  }, // openImageSelectionDialog
5224
5225  // 画像選択ダイアログを閉じる
5226  closeImageSelectionDialog: function() {
5227    prjutils.imageSelectionUploadButton = null;
5228    prjutils.imageSelectionProperties = [];
5229    prjutils.imageSelectionSelectedFunc = null;
5230
5231    if(prjutils.isAgent()) {
5232      let $dialog = $('#commonImageSelectionAgentDialog');
5233      agentCommon.closeFullDialog($dialog);
5234    } else {
5235      let $dialog = $('#commonImageSelectionDialog');
5236      prjutils.closeDialog($dialog);
5237    }
5238  }, // closeImageSelectionDialog
5239
5240  // 画像選択ダイアログ内に選択肢を表示する
5241  // imageSelectionType {int} 共通画像種別
5242  loadImageSelection: function(imageSelectionType) {
5243    let $dialog = $('#commonImageSelectionDialog');
5244    let currentSelectionType = $dialog.attr('data-selection_type');
5245    if(currentSelectionType == imageSelectionType) {
5246      // 読み込み済
5247      return;
5248    }
5249
5250    // 選択肢クリア
5251    $dialog.find('.image_selection_box.copy').remove();
5252
5253    let postParam = new FormData();
5254    postParam.append('image_selection_type', imageSelectionType);
5255    postParam.append('action', 'loadImageSelectionPost');
5256    $.ajax({
5257      xhr: prjutils.ajaxXhr,
5258      type: 'POST',
5259      url: binds['ajaxurl'],
5260      data: postParam,
5261      contentType : false,
5262      processData : false,
5263      success: function(res) {
5264        if(res['success'] === true) {
5265          $dialog.attr('data-selection_type', imageSelectionType);
5266          let list = res['list'];
5267          let $aera = $dialog.find('.selection_area');
5268          for(let imageMap of list) {
5269            prjutils.addImageSelectionBox(imageMap, $aera);
5270          }
5271        } else if(res['error']){
5272          alert_msg(res['error'], null);
5273        } else {
5274          salert_msg(unknown_error, null);
5275        }
5276      }
5277    })
5278    .fail(prjutils.ajaxFail)
5279    .always(prjutils.loadingImgDel);
5280  }, // loadImageSelection
5281
5282  // 画像選択ダイアログ内に選択肢を表示する
5283  // imageSelectionType {int} 共通画像種別
5284  loadImageSelectionAgent: function(imageSelectionType) {
5285    let $dialog = $('#commonImageSelectionAgentDialog');
5286    let currentSelectionType = $dialog.attr('data-selection_type');
5287    if(currentSelectionType == imageSelectionType) {
5288      // 読み込み済
5289      return;
5290    }
5291
5292    // 選択肢クリア
5293    $dialog.find('.image_selection_box.copy').remove();
5294
5295    let postParam = new FormData();
5296    postParam.append('image_selection_type', imageSelectionType);
5297    postParam.append('action', 'loadImageSelectionPost');
5298    $.ajax({
5299      xhr: prjutils.ajaxXhr,
5300      type: 'POST',
5301      url: binds['ajaxurl'],
5302      data: postParam,
5303      contentType : false,
5304      processData : false,
5305      success: function(res) {
5306        if(res['success'] === true) {
5307          $dialog.attr('data-selection_type', imageSelectionType);
5308          let list = res['list'];
5309          let $aera = $dialog.find('.selection_area');
5310          for(let imageMap of list) {
5311            prjutils.addImageSelectionBox(imageMap, $aera);
5312          }
5313        } else if(res['error']){
5314          alert_msg(res['error'], null);
5315        } else {
5316          salert_msg(unknown_error, null);
5317        }
5318      }
5319    })
5320    .fail(prjutils.ajaxFail)
5321    .always(prjutils.loadingImgDel);
5322  }, // loadImageSelectionAgent
5323
5324  // 画像選択ダイアログ内に選択肢を表示する
5325  addImageSelectionBox: function(image, $area) {
5326    let $template = $area.find('.template');
5327    let $newBox = $template.clone();
5328    $newBox.removeClass('template');
5329    $newBox.addClass('copy');
5330    $newBox.attr('data-selection_image_id', image['id']);
5331    let imageMedia = JSON.parse(image['image_json']);
5332    $newBox.find('.image_json').text(image['image_json']);
5333    $newBox.find('img').attr('src', binds['contenturl'] + imageMedia['path']);
5334    $newBox.find('.superimage_title').text(image['title']);
5335    $area.append($newBox);
5336  }, // addImageSelectionBox
5337
5338  // 画像選択ダイアログではなく直接メディアフォルダから選択する
5339  forwardImageSelection: function() {
5340    if(prjutils.imageSelectionUploadButton) {
5341      prjutils.imageSelectionUploadButton.trigger('click');
5342    }
5343  }, // forwardImageSelection
5344  
5345  // ダイアログ内の画像1つを選択した時
5346  selectedImageSelection: function(element) {
5347    if(prjutils.imageSelectionSelectedFunc) {
5348      prjutils.imageSelectionSelectedFunc(element);
5349    }
5350  }, // selectedImageSelection
5351
5352  // ダイアログ内の画像1つを選択した時:1枚選択用
5353  selectedImageSelectionSingle: function(element) {
5354    let $box = $(element);
5355    let json = $box.find('.image_json').text();
5356    let imageMedia = JSON.parse(json);
5357    let imageId = prjutils.imageSelectionProperties['post_name'];
5358
5359    let beforeFunc = prjutils.imageSelectionProperties['before_func'];
5360    if(beforeFunc) {
5361      beforeFunc(imageId);
5362    }
5363
5364    let $image = $('#' + imageId);
5365    let $form = $image.closest('form');
5366    if($form.length == 0 && prjutils.imageSelectionUploadButton) {
5367      $form = prjutils.imageSelectionUploadButton.closest('form');
5368    }
5369    
5370    // バイナリ選択のクリア
5371    delImage(imageId);
5372
5373    let fixIndex = '';
5374    if(prjutils.imageSelectionProperties['fix_index']) {
5375      fixIndex = '_' + prjutils.imageSelectionProperties['fix_index'];
5376    }
5377    
5378    // json情報埋め込み
5379    let inputFileId = imageId + '_FILE';
5380    let inputJsonId = imageId + '_JSON';
5381    $('#' + inputFileId).remove();
5382    let $inputJson = $form.find('#' + inputJsonId + ', input[name="' + inputJsonId + fixIndex + '"]').first();
5383    if($inputJson.length == 0) {
5384      $inputJson = $('<input type="hidden" id="' + inputJsonId + '" name="' + inputJsonId + fixIndex + '" class="none">');
5385      $form.append($inputJson);
5386    }
5387    $inputJson.attr('id', inputJsonId);
5388    $inputJson.val(json);
5389    $inputJson.trigger('change');
5390  
5391    // プレビュー埋め込み
5392    putImage(imageId, getImageUrl(imageMedia));
5393
5394    prjutils.closeImageSelectionDialog();
5395  }, // selectedImageSelectionSingle
5396
5397  // ダイアログ内の画像1つを選択した時:テーブル形式に追加用
5398  selectedImageSelectionTable: function(element) {
5399    // ダイアログ動作設定
5400    let tbodyId = prjutils.imageSelectionProperties['tbody_id'];
5401    let remarks = prjutils.imageSelectionProperties['remarks'];
5402    let callbackFunc = prjutils.imageSelectionProperties['callback_func'];
5403    
5404    // ダイアログ内
5405    let $box = $(element);
5406    let json = $box.find('.image_json').text();
5407    let imageMedia = JSON.parse(json);
5408    
5409    // 親画面側
5410    let $tbody = $('#' + tbodyId);
5411    let $trSample = $tbody.find('tr.sample');
5412    let $tr = $trSample.clone();
5413    let $fileInput = $tr.find('input[type="file"]');
5414    
5415    // 行追加
5416    $tr.removeClass('sample');
5417    $tr.find('[data-name]').each(function(){
5418        let $this = $(this);
5419        $this.attr('name', $this.data('name'));
5420    });
5421    $tbody.append($tr);
5422
5423    // 備考の指定があれば表示
5424    let cnt = 0;
5425    $tbody.find('.remarks').each(function(){
5426        if(remarks && cnt < remarks.length) {
5427            $(this).text(remarks[cnt++]);
5428        } else {
5429            return;
5430        }
5431    });
5432
5433    // json情報埋め込み
5434    let fileAttrName = $fileInput.attr('name');
5435    let $fileJson = $tr.find('input[type="hidden"][name="' + fileAttrName + '_JSON"]');
5436    $fileJson.val(json);
5437
5438    // 画像表示
5439    let imageUrl = getImageUrl(imageMedia);
5440    putImageTable($tr, imageUrl, imageMedia['title']);
5441    if(callbackFunc){
5442        callbackFunc();
5443    }
5444
5445    prjutils.closeImageSelectionDialog();
5446  }, // selectedImageSelectionTable
5447
5448  // // 画像選択ダイアログ内で選択した状態で決定する
5449  // applyImageSelection: function() {
5450
5451  // }, // applyImageSelection
5452  // // 画像選択ダイアログ内の選択状態を解除する
5453  // clearImageSelection: function() {
5454
5455  // },  // clearImageSelection
5456
5457  // 施設備品予約 編集ダイアログをクリアする
5458  clearFacilityReservationDialog: function() {
5459    let $form = $('#formDialogFacility');
5460    
5461    // 部活リスト
5462    let $selectClub = $('#dialogFacilityReservation_clubCd');
5463    $selectClub.empty();
5464    $('#side .club-list').each(function() {
5465      let $liClub = $(this);
5466      let clubCd = $liClub.attr('data-club_cd');
5467      let clubName = $liClub.find('.club_name').text();
5468      let $option = $('<option></option>');
5469      $option.val(clubCd);
5470      $option.text(clubName);
5471      $selectClub.append($option);
5472    });
5473
5474    $('#dialogFacilityReservation_clubName').empty();
5475
5476    prjutils.clearFormValues($form);
5477  },
5478
5479  // 施設備品予約 ダイアログを開く
5480  openFacilityReservationDialog: function(element) {
5481    let $dialog = $('#dialogFacilityReservation');
5482    activeValidationJ($dialog);
5483    prjutils.clearFacilityReservationDialog();
5484    let $this = $(element);
5485    let $tr = $this.closest('tr');
5486    let $td = $this.closest('td');
5487    let ymd = $td.attr('data-ymd');
5488
5489    let facilityJson = $tr.find('.facility_json').first().text();
5490    let facilityMap = [];
5491    if(facilityJson) {
5492      facilityMap = JSON.parse(facilityJson);
5493    }
5494    
5495    let displayMode = 'new';
5496    if($this.is('.facility_reservation_plus')) {
5497      // 新規
5498      prjutils.setFacilityReservationDialog(facilityMap, ymd);
5499    } else {
5500      // 更新
5501      displayMode = 'edit';
5502      let $facility = $this.closest('.facility_reservation_div');
5503      let json = $facility.find('.facility_reservation_json').text();
5504      if(json) {
5505        let map = JSON.parse(json);
5506        prjutils.setFacilityReservationDialog(facilityMap, ymd, map);
5507      }
5508    }
5509    prjutils.resetTextCounter($dialog);
5510    prjutils.showDialog($dialog, displayMode);
5511    $('#dialogFacilityReservation_endDate').trigger('change');
5512  },
5513
5514  // 施設備品予約ダイアログに編集対象を設定する
5515  setFacilityReservationDialog: function(facilityMap, ymd, map) {
5516    let $selectClubCd = $('#dialogFacilityReservation_clubCd');
5517    let $divClubName = $('#dialogFacilityReservation_clubName');
5518    let $bookingNote = $('#dialogFacilityReservation_bookingNote');
5519    let $usageNote = $('#dialogFacilityReservation_usageNote');
5520
5521    let isForSchool = $('#content.school_reservations').length > 0;
5522    if(isForSchool) {
5523      $selectClubCd.removeClass('none');
5524      $divClubName.addClass('none');
5525    } else {
5526      $selectClubCd.addClass('none');
5527      $divClubName.removeClass('none');
5528    }
5529    // デフォルト値
5530    $('#dialogFacilityReservation_facilityTitle').text(facilityMap['title']);
5531    $('#dialogFacilityFacilityId').val(facilityMap['id']);
5532    $selectClubCd.val($selectClubCd.attr('data-default'));
5533    $divClubName.text($divClubName.attr('data-default'));  
5534    $('#dialogFacilityReservation_title').val('');
5535    $('#dialogFacilityReservation_startDate').val(ymd);
5536    $('#dialogFacilityReservation_startHour').val(9);
5537    $('#dialogFacilityReservation_startMinute').val(0);
5538    $('#dialogFacilityReservation_endDate').val(ymd);
5539    $('#dialogFacilityReservation_endHour').val(16);
5540    $('#dialogFacilityReservation_endMinute').val(0);
5541    
5542    $bookingNote.text(facilityMap['booking_note']);
5543    if(facilityMap['booking_note']) {
5544      $bookingNote.closest('.input_row').removeClass('none');
5545    } else {
5546      $bookingNote.closest('.input_row').addClass('none');
5547    }
5548
5549    $usageNote.text(facilityMap['usage_note']);
5550    if(facilityMap['usage_note']) {
5551      $usageNote.closest('.input_row').removeClass('none');
5552    } else {
5553      $usageNote.closest('.input_row').addClass('none');
5554    }
5555
5556    if(map) {
5557      $('#dialogFacilityReservationId').val(map['id']);
5558      $selectClubCd.val(map['club_cd']);
5559      $divClubName.text(map['club_name']);      
5560      $('#dialogFacilityReservation_title').val(map['reserve_title']);
5561      let startDateTime = map['use_start_datetime'].split(' ');
5562      $('#dialogFacilityReservation_startDate').val(startDateTime[0]);
5563      $('#dialogFacilityReservation_startHour').val(prjutils.formatH(startDateTime[1]));
5564      $('#dialogFacilityReservation_startMinute').val(prjutils.formatM(startDateTime[1]));
5565      let endDateTime = map['use_end_datetime'].split(' ');
5566      $('#dialogFacilityReservation_endDate').val(endDateTime[0]);
5567      $('#dialogFacilityReservation_endHour').val(prjutils.formatH(endDateTime[1]));
5568      $('#dialogFacilityReservation_endMinute').val(prjutils.formatM(endDateTime[1]));
5569    }
5570    let $dialog = $('#formDialogFacilityReservation');
5571    clearValidationMessage($dialog);
5572  },
5573
5574  // 施設備品予約を保存する
5575  saveFacilityReservation: function() {
5576    save_confirm_msg(write_caution, function () {
5577      let $form = $('#formDialogFacilityReservation');
5578      let postParam = new FormData($form.get(0));
5579      postParam.append('action', 'editFacilityReservationPost');
5580      postParam.append('club_cd', $('#club_cd').val());
5581      
5582      $.ajax({
5583        xhr: prjutils.ajaxXhr,
5584        type: 'POST',
5585        url: binds['ajaxurl'],
5586        data: postParam,
5587        contentType: false,
5588        processData: false,
5589        success: function(res){
5590          prjutils.loadingImgDel();
5591          if(res['success'] === true){
5592            success_msg(success_save, function(){
5593              location.reload();
5594            });
5595          } else if(res['error']){
5596            alert_msg(res['error'], null);
5597          } else {
5598            alert_msg(unknown_error, null);
5599          }
5600        }
5601      })
5602      .fail(prjutils.ajaxFail)
5603      .always(prjutils.loadingImgDel);
5604    });
5605  },
5606
5607  // 施設備品予約を削除する
5608  deleteFacilityReservation: function() {
5609    delete_confirm_msg(delete_caution, function () {
5610      let $form = $('#formDialogFacilityReservation');
5611      let postParam = new FormData($form.get(0));
5612      postParam.append('action', 'deleteFacilityReservationPost');
5613      let $clubCd = $('#club_cd');
5614      if($clubCd.length > 0) {
5615        postParam.append('club_cd', $clubCd.val());
5616      }
5617      
5618      $.ajax({
5619        xhr: prjutils.ajaxXhr,
5620        type: 'POST',
5621        url: binds['ajaxurl'],
5622        data: postParam,
5623        contentType: false,
5624        processData: false,
5625        success: function(res){
5626          prjutils.loadingImgDel();
5627          if(res['success'] === true){
5628            success_msg(success_delete, function(){
5629              location.reload();
5630            });
5631          } else if(res['error']){
5632            alert_msg(res['error'], null);
5633          } else {
5634            alert_msg(unknown_error, null);
5635          }
5636        }
5637      })
5638      .fail(prjutils.ajaxFail)
5639      .always(prjutils.loadingImgDel);
5640    });
5641  },
5642
5643  // 施設備品予約 ReadOnlyダイアログを開く
5644  openFacilityReservationReadOnlyDialog: function(element) {
5645    let $dialog = $('#dialogFacilityReservationReadOnly');
5646    $dialog.removeClass('on_monthly');
5647    if(element) {
5648      let $this = $(element);
5649      let $reservation = $this.closest('.facility_reservation_div');
5650      let json = $reservation.find('.facility_reservation_json').text();
5651      let reservation = JSON.parse(json);
5652      let facilityId = reservation['facility_id'];
5653      let $tr = $('#facilityDiv tr[data-facility_id="' + facilityId + '"]');
5654      json = $tr.find('.facility_json').text();
5655      let facility = JSON.parse(json);
5656      if($('#facilityMonthlyDialog:visible').length > 0) {
5657        $dialog.addClass('on_monthly');
5658      }
5659
5660      prjutils.setFacilityReservationReadOnlyDialog(reservation, facility); 
5661    }
5662    prjutils.showDialog($dialog);
5663  },
5664  // 施設備品予約 ReadOnlyダイアログ内容を設定する
5665  setFacilityReservationReadOnlyDialog: function(reservation, facility) {
5666    $('#dialogFacilityReservationReadOnly_facilityTitle').text(facility['title']);
5667    $('#dialogFacilityReservationReadOnly_clubName').text(reservation['club_name']);
5668    // 用途
5669    let $title = $('#dialogFacilityReservationReadOnly_title');
5670    $title.text(reservation['reserve_title']);
5671
5672    // 日時
5673    $('#dialogFacilityReservationReadOnly_startDate').text(reservation['use_start_date_str']);
5674    $('#dialogFacilityReservationReadOnly_startTime').text(reservation['use_start_time_str']);
5675    $('#dialogFacilityReservationReadOnly_endDate').text(reservation['use_end_date_str']);
5676    $('#dialogFacilityReservationReadOnly_endTime').text(reservation['use_end_time_str']);
5677    
5678    // 画像
5679    let $img = $('#dialogFacilityReservationReadOnly_image');
5680    $img.addClass('none');
5681    if(facility['image_json']) {
5682      let image = JSON.parse(facility['image_json']);
5683      $img.attr('src', binds['contenturl'] + image['path']);
5684      $img.removeClass('none');
5685    }
5686
5687    // 予約時の注意事項
5688    let $bookingDiv = $('#dialogFacilityReservationReadOnly_bookingNoteDiv');
5689    let booking = facility['booking_note'];
5690    if(booking) {
5691      $bookingDiv.removeClass('none');
5692    } else {
5693      $bookingDiv.addClass('none');
5694    }
5695    $('#dialogFacilityReservationReadOnly_bookingNote').text(booking);
5696
5697    // 使用時の注意事項
5698    let $usageNoteDiv = $('#dialogFacilityReservationReadOnly_usageNoteDiv');
5699    let usage = facility['usage_note'];
5700    if(usage) {
5701      $usageNoteDiv.removeClass('none');
5702    } else {
5703      $usageNoteDiv.addClass('none');
5704    }
5705    $('#dialogFacilityReservationReadOnly_usageNote').text(usage);
5706  },
5707
5708  // ウィンドウ横幅がPCサイズであるか判定する
5709  isPC: function() {
5710    return window.innerWidth > 767;
5711  }, // isPC
5712
5713  // アプリ版(agent配下)であるかを判定する
5714  isAgent: function() {
5715    let currentPath = location.pathname;
5716    let res = currentPath.startsWith('/agent/');
5717    return res;
5718  }, // isAgent
5719
5720  // テーブル上マウス横移動操作
5721  bindTableDrag: function() {
5722    $('.scrollWrap').each(function() {
5723      const $wrapper = $(this);
5724      let isDown = false;
5725      let startX;
5726      let scrollLeft;
5727      let isDragging = false;
5728
5729      $wrapper.on('mousedown', function(e) {
5730          if (e.which !== 1) return; // 左クリックのみ
5731          
5732          // 新しいドラッグ開始のため既存の選択をクリア
5733          if (window.getSelection) {
5734              window.getSelection().removeAllRanges();
5735          } else if (document.selection) {
5736              document.selection.empty();
5737          }
5738
5739          isDown = true;
5740          startX = e.pageX - $wrapper.offset().left;
5741          scrollLeft = $wrapper.scrollLeft();
5742          isDragging = false;
5743      });
5744
5745      $wrapper.on('mouseup mouseleave', function() {
5746          isDown = false;
5747          isDragging = false;
5748          $wrapper.removeClass('is-dragging');
5749      });
5750
5751      $wrapper.on('mousemove', function(e) {
5752          if (!isDown) return;
5753
5754          if (isDragging) {
5755              //e.preventDefault();
5756              const x = e.pageX - $wrapper.offset().left;
5757              const walk = (x - startX); 
5758              $wrapper.scrollLeft(scrollLeft - walk);
5759              return;
5760          }
5761
5762          const currentX = e.pageX - $wrapper.offset().left;
5763          const walkX = currentX - startX;
5764
5765          // 15px 以上の移動で判定
5766          if (Math.abs(walkX) > 12) {
5767              // 選択範囲があるかチェック
5768              const selection = window.getSelection();
5769              if (selection && selection.toString().length > 0) {
5770                  // テキスト選択が行われているので、スクロールは開始しない
5771                  isDown = false;
5772                  return;
5773              }
5774
5775              // スクロール開始
5776              isDragging = true;
5777              $wrapper.addClass('is-dragging');
5778          }
5779      });
5780    });
5781  },
5782
5783  // ブラウザがGoogle Chromeか判定する
5784  isChrome: function() {
5785    var ua = navigator.userAgent || '';
5786    if (/Edg|EdgiOS|EdgA/i.test(ua)) {
5787      return false;
5788    }
5789    if (/OPR|Opera/i.test(ua) || /SamsungBrowser/i.test(ua) || /Brave/i.test(ua) || /Firefox|FxiOS/i.test(ua)) {
5790      return false;
5791    }
5792    if (/Chrome|CriOS/i.test(ua)) {
5793      return true;
5794    }
5795    return false;
5796  },
5797
5798  /**
5799   * CAPTCHA (Cloudflare Turnstile) を初期化する共通関数。
5800   * 
5801   * @param {object} obj 対象のログインオブジェクト (例: naviLogin, fanLogin など)
5802   * @param {string} formId ログインフォームのID (デフォルト: 'loginform')
5803   * @param {function} callbackFunc トークン取得成功時に実行するコールバック関数 (トークンが引数に渡されます)
5804   */
5805  setupCaptcha: function(obj, formId, callbackFunc) {
5806    if (!formId) {
5807      formId = 'loginform';
5808    }
5809
5810    var containerId = formId + '-captcha-container';
5811    
5812    if (typeof CAPTCHA_SITE_KEY === 'undefined' || isEmpty(CAPTCHA_SITE_KEY)) {
5813      obj.captchaWidgetId = null;
5814      obj.availLoginForm = true;
5815      obj.captchaLogin = function() {
5816        var args = Array.prototype.slice.call(arguments);
5817        args.push('');
5818        if (callbackFunc) {
5819          callbackFunc.apply(obj, args);
5820        }
5821      };
5822      return;
5823    }
5824    
5825    obj.captchaWidgetId = null;
5826    obj.availLoginForm = false;
5827    
5828    var initCaptchaWidget = function() {
5829      var $form = $('#' + formId);
5830      if ($form.length === 0) {
5831        return;
5832      }
5833      
5834      // コンテナの生成
5835      if ($('#' + containerId).length === 0) {
5836        var $submit = $form.find('.login-submit, #commonLoginSubmit, #wp-submit, input[name="wp-submit"]').first();
5837        if ($submit.length > 0) {
5838          var $target = $submit.parent().is($form) ? $submit : $submit.parent();
5839          $target.before('<div id="' + containerId + '" style="display:none; margin-bottom: 15px;"></div>');
5840        } else {
5841          $form.append('<div id="' + containerId + '" style="display:none;"></div>');
5842        }
5843      }
5844      obj.captchaWidgetId = turnstile.render('#' + containerId, {
5845        sitekey: CAPTCHA_SITE_KEY,
5846        execution: 'execute',
5847        retry: 'never',
5848        callback: function(token) {
5849          $('#' + containerId).hide();
5850          if (callbackFunc) {
5851            var args = [];
5852            if (obj._captchaArgs) {
5853              args = Array.prototype.slice.call(obj._captchaArgs);
5854            }
5855            args.push(token);
5856            // トークン取得に成功し、ログイン処理に入るため、再度ローディングを表示・AJAX完了イベントを設定
5857            prjutils.loadingImg();
5858            $('.loader_text').text('認証中');
5859            $(document).off('ajaxComplete').one('ajaxComplete', function() {
5860              prjutils.loadingImgDel();
5861            });
5862            callbackFunc.apply(obj, args);
5863          }
5864        },
5865        'before-interactive-callback': function() {
5866          $('#' + containerId).show();
5867          prjutils.loadingImgDel(); // インタラクティブ検証が表示された場合はローディングを非表示にする
5868          $(document).off('ajaxComplete');
5869        },
5870        'error-callback': function(errorCode) {
5871          prjutils.loadingImgDel(); // セキュリティ検証エラー時はローディングを非表示にする
5872          $(document).off('ajaxComplete');
5873          var criticalErrors = ['110100', '110110', '110200', '200100', '400020', '400070'];
5874          if (criticalErrors.indexOf(String(errorCode)) === -1) {
5875            $('#' + containerId).hide();
5876            turnstile.reset(obj.captchaWidgetId);
5877          }
5878          if (!prjutils.isChrome()) {
5879            var msg = '<div style="white-space: pre-wrap; word-break: break-word; line-height: 1.6; text-align: left;">'
5880              + "セキュリティ検証に失敗しました。\n\n"
5881              + "当サービスはGoogle Chromeを推奨しております。\n"
5882              + '<a href="https://www.google.com/chrome/" target="_blank" rel="noopener noreferrer" style="color:#286090; text-decoration:underline; font-weight:bold;">Google Chromeのダウンロードはこちら</a>\n\n(' + errorCode + ')</div>';
5883            alert_msg_html(msg, null);
5884          } else {
5885            alert_msg_html("セキュリティ検証に失敗しました。\nもう一度お試しください。(" + errorCode + ")", null);
5886          }
5887        },
5888        'expired-callback': function() {
5889          prjutils.loadingImgDel(); // セキュリティ検証の有効期限切れ時はローディングを非表示にする
5890          $(document).off('ajaxComplete');
5891          $('#' + containerId).hide();
5892          turnstile.reset(obj.captchaWidgetId);
5893        },
5894        'unsupported-callback': function() {
5895          prjutils.loadingImgDel(); // ブラウザ非対応時はローディングを非表示にする
5896          $(document).off('ajaxComplete');
5897          if (!prjutils.isChrome()) {
5898            var msg = '<div style="white-space: pre-wrap; word-break: break-word; line-height: 1.6; text-align: left;">'
5899              + "お使いのブラウザはセキュリティ検証に対å¿
5899œã—ていません。\n\n"
5900              + "当サービスはGoogle Chromeを推奨しております。\n"
5901              + '<a href="https://www.google.com/chrome/" target="_blank" rel="noopener noreferrer" style="color:#286090; text-decoration:underline; font-weight:bold;">Google Chromeのダウンロードはこちら</a></div>';
5902            alert_msg_html(msg, null);
5903          } else {
5904            alert_msg_html("お使いのブラウザはセキュリティ検証に対応していません。\n別のブラウザでお試しください。", null);
5905          }
5906        }
5907      });
5908      obj.availLoginForm = true;
5909    };
5910    
5911    if (typeof turnstile !== 'undefined') {
5912      initCaptchaWidget();
5913    } else {
5914      var checkCaptcha = setInterval(function() {
5915        if (typeof turnstile !== 'undefined') {
5916          clearInterval(checkCaptcha);
5917          initCaptchaWidget();
5918        }
5919      }, 100);
5920    }
5921    
5922    // オブジェクトにcaptchaLoginを実行するメソッドを追加定義
5923    obj.captchaLogin = function() {
5924      if (!obj.availLoginForm || !obj.captchaWidgetId) {
5925        return;
5926      }
5927
5928      // インタラクティブなセキュリティチェックが表示中の場合は、チェック完了を促す
5929      if ($('#' + containerId).is(':visible')) {
5930        alert_msg_html('『私はロボットではありません』\nにチェックを入れてください。', null);
5931        return;
5932      }
5933
5934      obj._captchaArgs = arguments;
5935      
5936      // ローディング画像を表示
5937      prjutils.loadingImg();
5938      $('.loader_text').text('認証中');
5939      
5940      // AJAX完了時にローディングを非表示にするイベントを設定 (1回のみ実行)
5941      $(document).off('ajaxComplete').one('ajaxComplete', function() {
5942        prjutils.loadingImgDel();
5943      });
5944
5945      try {
5946        // 毎回実行前にリセットして、古いトークンの使い回し(timeout-or-duplicate)を防ぐ
5947        turnstile.reset(obj.captchaWidgetId);
5948        turnstile.execute(obj.captchaWidgetId);
5949      } catch (e) {
5950        // 想定外のJSエラー発生時はローディングを非表示にする
5951        prjutils.loadingImgDel();
5952        $(document).off('ajaxComplete');
5953        console.error(e);
5954      }
5955    };
5956  },
5957
5958  // 画面要素(日付・時・分)からDateオブジェクトを生成する
5959  getDatetimeFromElements: function(dateSelector, hourSelector, minuteSelector) {
5960    let date = $(dateSelector).val();
5961    let hour = $(hourSelector).val();
5962    let minute = $(minuteSelector).val();
5963    if(!date || !hour || !minute) {
5964      return null;
5965    }
5966    let dates = date.split('-');
5967    return new Date(dates[0], dates[1] - 1, dates[2], hour, minute, 0);
5968  },
5969
5970  // 過去日時エラーを取得する
5971  getPastDatetimeError: function(datetime) {
5972    if(datetime && datetime < new Date()) {
5973      return '過去の日時を選択しています';
5974    }
5975    return '';
5976  },
5977
5978  // 開催日時と予約期間の前後関係エラーを取得する
5979  getScheduleDatetimeError: function(scheduleStart, scheduleEnd, reservableStart, reservableEnd) {
5980    let errors = {};
5981    if(scheduleStart && scheduleEnd && scheduleEnd <= scheduleStart) {
5982      errors.schedule = '終了時刻は開始時刻より後を入力してください。';
5983    }
5984    if(!reservableStart || !reservableEnd) {
5985      errors.reservable = '必須入力です。';
5986    } else if(reservableEnd <= reservableStart) {
5987      errors.reservable = '予約期間の終了日時は開始日時より後を入力してください。';
5988    } else if(scheduleStart) {
5989      if(scheduleStart < reservableStart || scheduleStart < reservableEnd) {
5990        errors.relation = '予約期間は開催日時より前の日時を入力してください。';
5991      }
5992    }
5993    return Object.keys(errors).length > 0 ? errors : null;
5994  },
5995}; // prjutils
5996
5997// 以下common.jsから移行
5998function alertMovePage(confirmFunc, cancelFunc){
5999  swal({
6000      title:'', // タイトル文
6001      type:"error", // default:null "warning","error","success","info"
6002      text:error_move_page, //説明文
6003      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6004      showCancelButton: true, // default:false キャンセルボタンの有無
6005      confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6006      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6007      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6008  }).then(confirmFunc, cancelFunc).catch(swal.noop);
6009}
6010
6011function alert_msg(msg, confirmFunc){
6012  // 改行タグがあればalert_msg_html()を実行
6013  if(typeof msg === 'string' && msg.match(/<br>/)) {
6014    alert_msg_html(msg, confirmFunc);
6015    return;
6016  }
6017  swal({
6018          title:'', // タイトル文
6019          type:"error", // default:null "warning","error","success","info"
6020          text:msg, //説明文
6021          // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6022          confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6023          confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6024      }).then(confirmFunc).catch(swal.noop);
6025}
6026
6027function alert_msg_html(msg, confirmFunc){
6028  swal({
6029      title:'', // タイトル文
6030      type:"error", // default:null "warning","error","success","info"
6031      html:'<pre>' + msg + '</pre>', //説明文
6032      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6033      confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6034      confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6035  }).then(confirmFunc).catch(swal.noop);
6036}
6037
6038function plan_alert_msg2(msg, confirmFunc){
6039  swal({
6040      title:'', // タイトル文
6041      type:"error", // default:null "warning","error","success","info"
6042      text:msg, //説明文
6043      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6044      confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6045      confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6046  }).then(confirmFunc).catch(swal.noop);
6047}
6048
6049// デザインUPした情報メッセージ
6050function info_msg_html(msg, confirmFunc, strTitle) {
6051  swal({
6052    title:strTitle ? strTitle : '', // タイトル文(デフォルトは空)
6053    type:"info", // default:null "warning","error","success","info"
6054    html:'<pre class="main-text">' + msg + '</pre>', //説明文
6055    // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6056    confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6057    confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6058  }).then(confirmFunc).catch(swal.noop);
6059}
6060
6061function confirm_msg_html(msg, confirmFunc){
6062  confirm_msg('<pre>' + msg + '</pre>', confirmFunc)
6063}
6064function confirm_msg(msg, confirmFunc){
6065  swal({
6066          title:'', // タイトル文
6067          type:"info", // default:null "warning","error","success","info"
6068          html:msg, //説明文
6069          // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6070          showCancelButton: true, // default:false キャンセルボタンの有無
6071          confirmButtonText:"はい", // default:"OK" 確認ボタンの文言
6072          confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6073          cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6074      }).then(confirmFunc).catch(swal.noop);
6075}
6076
6077// 確認ボタンのみのメッセージ(htmlタグ有効版)
6078function confirm_onemsg_html(msg, confirmFunc, iconType = 'info'){
6079  confirm_onemsg('<pre>' + msg + '</pre>', confirmFunc, iconType);
6080}
6081// 確認ボタンのみのメッセージ
6082function confirm_onemsg(msg, confirmFunc, iconType = 'info'){
6083  swal({
6084          title:'', // タイトル文
6085          type:iconType, // default:null "warning","error","success","info"
6086          html:msg, //説明文
6087          // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6088          //showCancelButton: true, // default:false キャンセルボタンの有無
6089          confirmButtonText: "確認", // default:"OK" 確認ボタンの文言
6090          confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6091      }).then(confirmFunc).catch(swal.noop);
6092}
6093
6094function password_confirm_msg(msg, confirmFunc){
6095  swal({
6096      title:'', // タイトル文
6097      type:"info", // default:null "warning","error","success","info"
6098      text:msg, //説明文
6099      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6100      showCancelButton: true, // default:false キャンセルボタンの有無
6101      confirmButtonText:"変更する", // default:"OK" 確認ボタンの文言
6102      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6103      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6104  }).then(confirmFunc).catch(swal.noop);
6105}
6106
6107function save_confirm_msg(msg, confirmFunc){
6108  swal({
6109      title:'', // タイトル文
6110      type:"info", // default:null "warning","error","success","info"
6111      text:msg, //説明文
6112      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6113      showCancelButton: true, // default:false キャンセルボタンの有無
6114      confirmButtonText:"保存する", // default:"OK" 確認ボタンの文言
6115      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6116      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6117  }).then(confirmFunc).catch(swal.noop);
6118}
6119
6120//差し戻し
6121function revert_confirm_msg(msg, confirmFunc){
6122  swal({
6123      title:'', // タイトル文
6124      type:"info", // default:null "warning","error","success","info"
6125      text:msg, //説明文
6126      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6127      showCancelButton: true, // default:false キャンセルボタンの有無
6128      confirmButtonText:"差し戻す", // default:"OK" 確認ボタンの文言
6129      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6130      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6131  }).then(confirmFunc).catch(swal.noop);
6132}
6133
6134//保存(承認依頼)
6135function approvalRequest_confirm_msg(msg, confirmFunc){
6136  swal({
6137      title:'', // タイトル文
6138      type:"info", // default:null "warning","error","success","info"
6139      text:msg, //説明文
6140      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6141      showCancelButton: true, // default:false キャンセルボタンの有無
6142      confirmButtonText:"保存(承認依頼)", // default:"OK" 確認ボタンの文言
6143      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6144      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6145  }).then(confirmFunc).catch(swal.noop);
6146}
6147
6148function copy_confirm_msg(msg, confirmFunc){
6149  swal({
6150      title:'', // タイトル文
6151      type:"info", // default:null "warning","error","success","info"
6152      text:msg, //説明文
6153      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6154      showCancelButton: true, // default:false キャンセルボタンの有無
6155      confirmButtonText:"コピーする", // default:"OK" 確認ボタンの文言
6156      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6157      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6158  }).then(confirmFunc).catch(swal.noop);
6159}
6160
6161function send_confirm_msg(msg, confirmFunc){
6162  swal({
6163      title:'', // タイトル文
6164      type:"info", // default:null "warning","error","success","info"
6165      text:msg, //説明文
6166      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6167      showCancelButton: true, // default:false キャンセルボタンの有無
6168      confirmButtonText:"送信する", // default:"OK" 確認ボタンの文言
6169      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6170      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6171  }).then(confirmFunc).catch(swal.noop);
6172}
6173
6174function catch_confirm_msg(confirmFunc){
6175  swal({
6176      title:'', // タイトル文
6177      type:"info", // default:null "warning","error","success","info"
6178      text:"動画にキャッチコピーを表示しますか?", //説明文
6179      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6180      showCancelButton: true, // default:false キャンセルボタンの有無
6181      confirmButtonText:"表示する", // default:"OK" 確認ボタンの文言
6182      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6183      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6184  }).then(confirmFunc).catch(swal.noop);
6185}
6186
6187function delete_confirm_msg(msg, confirmFunc){
6188  swal({
6189      title:'', // タイトル文
6190      type:"info", // default:null "warning","error","success","info"
6191      html: msg.replace(/\n/g, '<br>'), // 説明分
6192      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6193      showCancelButton: true, // default:false キャンセルボタンの有無
6194      confirmButtonText:"削除する", // default:"OK" 確認ボタンの文言
6195      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6196      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6197  }).then(confirmFunc).catch(swal.noop);
6198}
6199
6200function success_msg_html(msg, confirmFunc){
6201  swal({
6202    title:'', // タイトル文
6203    type:"success", // default:null "warning","error","success","info"
6204    html:'<pre>' + msg + '</pre>', //説明文
6205    allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6206    confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6207    confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6208  }).then(confirmFunc).catch(swal.noop);
6209}
6210
6211function success_msg(msg, confirmFunc){
6212  swal({
6213          title:'', // タイトル文
6214          type:"success", // default:null "warning","error","success","info"
6215          text:msg, //説明文
6216          allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6217          confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6218          confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6219      }).then(confirmFunc).catch(swal.noop);
6220}
6221
6222function source_success_msg(msg, confirmFunc){
6223  swal({
6224      title:'', // タイトル文
6225      type:"info", // default:null "warning","error","success","info"
6226      text:msg, //説明文
6227      showCancelButton: true,
6228      // allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6229      confirmButtonText:"バナーコードをコピーする", // default:"OK" 確認ボタンの文言
6230      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6231      cancelButtonText:"キャンセル"
6232  }).then(confirmFunc).catch(swal.noop);
6233}
6234
6235function warn_msg(msg, confirmFunc){
6236    // 起動元のボタンフォーカスを解除、色反転を戻すため
6237    $(document.activeElement).blur();
6238    swal({
6239        title:'', // タイトル文
6240        type:"warning", // default:null "warning","error","success","info"
6241        text:msg, //説明文
6242        allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6243        confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6244        confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6245    }).then(confirmFunc).catch(swal.noop);
6246}
6247
6248function warn_msg_html(msg, confirmFunc){
6249  swal({
6250      title:'', // タイトル文
6251      type:"warning", // default:null "warning","error","success","info"
6252      html:'<pre>' + msg + '</pre>', //説明文
6253      allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6254      confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6255      confirmButtonColor: "#286090" // default:"#AEDEF4" 確認ボタンの色
6256  }).then(confirmFunc).catch(swal.noop);
6257}
6258
6259function registration_msg(msg, confirmFunc){
6260  swal({
6261      title:'', // タイトル文
6262      type:"info", // default:null "warning","error","success","info"
6263      text:msg, //説明文
6264      allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6265      showCancelButton: true, // default:false キャンセルボタンの有無
6266      confirmButtonText:"登録する", // default:"OK" 確認ボタンの文言
6267      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6268      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6269  }).then(confirmFunc).catch(swal.noop);
6270}
6271
6272function defaultOrder_msg(msg, confirmFunc){
6273  swal({
6274      title:'', // タイトル文
6275      type:"info", // default:null "warning","error","success","info"
6276      text:msg, //説明文
6277      allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6278      showCancelButton: true, // default:false キャンセルボタンの有無
6279      confirmButtonText:"初期化する", // default:"OK" 確認ボタンの文言
6280      confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6281      cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6282  }).then(confirmFunc).catch(swal.noop);
6283}
6284
6285function menu_confirm_msg(msg, confirmFunc){
6286  swal({
6287      title:'', // タイトル文
6288      type:"info", // default:null "warning","error","success","info"
6289      //html: msg,
6290      html:msg + '<br>' + 
6291      '<button type="button" role="button" tabindex="0" class="SwalBtn1 customSwalBtn">' + 'はい' + '</button>' +
6292      '<button type="button" role="button" tabindex="0" class="SwalBtn2 customSwalBtn">' + 'いいえ' + '</button>' +
6293      '<button type="button" role="button" tabindex="0" class="SwalBtn3 customSwalBtn">' + 'キャンセル' + '</button>', //説明文
6294      //showConfirmButton:true,
6295      //allowOutsideClick:false, // default:false アラートの外を画面クリックでアラート削除
6296      //confirmButtonText:"確認", // default:"OK" 確認ボタンの文言
6297      //confirmButtonColor: "#286090", // default:"#AEDEF4" 確認ボタンの色
6298      //showCancelButton: true, // default:false キャンセルボタンの有無
6299      //cancelButtonText:"キャンセル"//, // キャンセルボタンの文言
6300      showCancelButton: false, 
6301      showConfirmButton: false
6302  }).then(confirmFunc).catch(swal.noop);
6303}
6304
6305/**
6306 * jQueryタイプの値検証処理を有効化する
6307 * @param {jQueryObject} $area 検証対象の入力項目をすべて含むタグ
6308 */
6309function activeValidationJ($area) {
6310  (function($){
6311      var $items = $area.find('[data-validation]');
6312      var $buttons = $area.find('[data-error-disabled]');
6313      $items.on('change keyup oninput', function (event, options) {
6314          var isUserAction = !!event.originalEvent;
6315          var forceShow = !!(options && options.forceShow);
6316          if (isUserAction || forceShow) {
6317              $area.data('validation_started', true);
6318          }
6319          var isSilent = !isUserAction
6320              && !forceShow
6321              && !$area.data('validation_started');
6322          var isTotalErr = false;
6323          $items.each(function () {
6324              var $this = $(this);
6325              var validationType = $this.attr('data-validation');
6326              var $errLabel = $('#' + $this.attr('data-err'));
6327              var $errLabelPast = $('#' + $this.attr('data-err-past'));
6328              var $errLabelSchedule = $('#' + $this.attr('data-err-schedule'));
6329              var $target = $('#' + $this.attr('data-target'));
6330              var $targetList = $($this.attr('data-target-list'));
6331              var types = validationType.split(' ');
6332              var val = $this.val();
6333              var isPastErrLabelSame = $errLabelPast.length > 0 && $errLabel.length > 0 && $errLabelPast[0] === $errLabel[0];
6334              var isScheduleErrLabelSameAsPast = $errLabelSchedule.length > 0 && $errLabelPast.length > 0 && $errLabelSchedule[0] === $errLabelPast[0];
6335              // 状態クリア
6336              $errLabel.text('');
6337              if ($errLabelPast.length > 0 && !isPastErrLabelSame && types.indexOf('schedule_past_datetime') >= 0) {
6338                $errLabelPast.text('').addClass('none');
6339              }
6340              if ($errLabelSchedule.length > 0 && !isScheduleErrLabelSameAsPast) {
6341                $errLabelSchedule.text('');
6342              }
6343
6344              // 検証スキップ条件
6345              if (types.indexOf('skipdisabled') >= 0) {
6346                if($this.is(':disabled')) {
6347                  return true; // continue;
6348                }
6349              }
6350              if (types.indexOf('skiphidden') >= 0) {
6351                if($this.is(':hidden')) {
6352                  return true; // continue;
6353                }
6354              }
6355
6356              if (types.indexOf('skipInit') >= 0) {
6357                  if (!$this.data('initialized')) {
6358                      $this.data('initialized', true);
6359                      return true;
6360                  }
6361              }
6362
6363              // 値検証
6364              var isErr = false;
6365              if (types.indexOf('schoolCd') >= 0) {
6366                  if (!isErr && isEmpty(val)) {
6367                      $errLabel.text("半角英数字とハイフンで入力してください。(40文字まで)");
6368                      isErr = true;
6369                  }
6370                  if (!isErr && !isschoolCd(val)) {
6371                      $errLabel.text("半角英数字とハイフンで入力してください。");
6372                      isErr = true;
6373                  }
6374              }
6375
6376              if (types.indexOf('required') >= 0) {
6377                  if (!isErr && isEmpty(val)) {
6378                      $errLabel.text("必須入力です。");
6379                      isErr = true;
6380                  }
6381              }
6382              if (types.indexOf('immediate_today') >= 0) {
6383                  let targetDate = $('#' + $this.attr('data-target-date')).val();
6384                  if (!isErr && val === 'immediate' && targetDate !== prjutils.getInputDate()) {
6385                      $errLabel.text("即時公開は本日のみ選択できます。");
6386                      isErr = true;
6387                  }
6388              }
6389              if (types.indexOf('required_chk') >= 0) {
6390                  if (!isErr && ($this.prop('checked') == false)) {
6391                      $errLabel.text("選択してください。");
6392                      isErr = true;
6393                  }
6394              }
6395              if (types.indexOf('required_chk_list') >= 0) {
6396                if (!isErr && ($targetList.filter(':checked').length == 0)) {
6397                    $errLabel.text("選択してください。");
6398                    isErr = true;
6399                }
6400              }
6401              if (types.indexOf('requiredSel') >= 0) {
6402                  if (!isErr && isEmpty(val)) {
6403                      $errLabel.text("選択してください。");
6404                      isErr = true;
6405                  }
6406              }
6407              if (types.indexOf('email') >= 0) {
6408                if (!isErr && !isEmail(val)) {
6409                    $errLabel.text("正しい形式のメールアドレスを入力してください。");
6410                    isErr = true;
6411                }
6412              }
6413              if (types.indexOf('email_csv') >= 0) {
6414                if (!isErr && !isEmailCsv(val)) {
6415                    $errLabel.text("正しい形式のメールアドレスを入力してください。カンマ(,)区切りで複数入力可能です。");
6416                    isErr = true;
6417                }
6418              }
6419              if (types.indexOf('datetime_range') >= 0) {
6420                let startYmd = $('#' + $this.attr('data-target-start-date')).val();
6421                let dateStartH = ($('#' + $this.attr('data-target-start-h')).val() ?? '').padStart(2, '0');
6422                let dateStartM = ($('#' + $this.attr('data-target-start-m')).val() ?? '').padStart(2, '0');
6423                let dateStart = startYmd + ' ' + dateStartH + ':' + dateStartM;
6424                let endYmd = val;
6425                let dateEndH = ($('#' + $this.attr('data-target-end-h')).val() ?? '').padStart(2, '0');
6426                let dateEndM = ($('#' + $this.attr('data-target-end-m')).val() ?? '').padStart(2, '0');
6427                let dateEnd = endYmd + ' ' + dateEndH + ':' + dateEndM;
6428                if(!isErr && startYmd && endYmd && dateStart >= dateEnd) {
6429                    $errLabel.text("終了日時は開始日時より後を入力してください。");
6430                    isErr = true;
6431                }
6432              }
6433              if (types.indexOf('datetime_before') >= 0) {
6434                let baseYmd = $('#' + $this.attr('data-target-before-d
6434ate')).val();
6435                let baseH = ($('#' + $this.attr('data-target-before-h')).val() ?? '').padStart(2, '0');
6436                let baseM = ($('#' + $this.attr('data-target-before-m')).val() ?? '').padStart(2, '0');
6437                let targetYmd = val;
6438                let targetH = ($('#' + $this.attr('data-target-end-h')).val() ?? '').padStart(2, '0');
6439                let targetM = ($('#' + $this.attr('data-target-end-m')).val() ?? '').padStart(2, '0');
6440                let baseDatetime = baseYmd + ' ' + baseH + ':' + baseM;
6441                let targetDatetime = targetYmd + ' ' + targetH + ':' + targetM;
6442                if(!isErr && baseYmd && targetYmd && targetDatetime >= baseDatetime) {
6443                    $errLabel.text($this.attr('data-err-before') || "日時の順序を正しく入力してください。");
6444                    isErr = true;
6445                }
6446              }
6447
6448              // 過去の開催日時を選んでいる場合は保存不可
6449              if (types.indexOf('schedule_past_datetime') >= 0) {
6450                let scheduleStart = prjutils.getDatetimeFromElements(
6451                  '#' + $this.attr('data-schedule-start-date'),
6452                  '#' + $this.attr('data-schedule-start-hour'),
6453                  '#' + $this.attr('data-schedule-start-minute')
6454                );
6455                let error = $this.attr('data-skip-past-datetime') === '1' ? '' : prjutils.getPastDatetimeError(scheduleStart);
6456                let $pastErrLabel = $errLabelPast.length > 0 ? $errLabelPast : $errLabel;
6457                if ($errLabelPast.length > 0 && !isPastErrLabelSame) {
6458                  $pastErrLabel.toggleClass('none', error === '');
6459                }
6460                if(error !== '') {
6461                  $pastErrLabel.text(error);
6462                  isErr = true;
6463                }
6464              }
6465
6466              // 開催日時と予約期間の前後関係チェック
6467              if (types.indexOf('schedule_datetime') >= 0) {
6468                let errors = prjutils.getScheduleDatetimeError(
6469                  prjutils.getDatetimeFromElements(
6470                    '#' + $this.attr('data-schedule-start-date'),
6471                    '#' + $this.attr('data-schedule-start-hour'),
6472                    '#' + $this.attr('data-schedule-start-minute')
6473                  ),
6474                  prjutils.getDatetimeFromElements(
6475                    '#' + $this.attr('data-schedule-start-date'),
6476                    '#' + $this.attr('data-schedule-end-hour'),
6477                    '#' + $this.attr('data-schedule-end-minute')
6478                  ),
6479                  prjutils.getDatetimeFromElements(
6480                    '#' + $this.attr('data-reservable-start-date'),
6481                    '#' + $this.attr('data-reservable-start-hour'),
6482                    '#' + $this.attr('data-reservable-start-minute')
6483                  ),
6484                  prjutils.getDatetimeFromElements(
6485                    '#' + $this.attr('data-reservable-end-date'),
6486                    '#' + $this.attr('data-reservable-end-hour'),
6487                    '#' + $this.attr('data-reservable-end-minute')
6488                  )
6489                );
6490
6491                if (!isErr && errors) {
6492                  let scheduleMessages = [errors.schedule].filter(Boolean);
6493                  let reservableMessages = [errors.reservable, errors.relation].filter(Boolean);
6494                  if ($errLabelSchedule.length > 0 && scheduleMessages.length > 0) {
6495                    if ($errLabelSchedule.text() === '') {
6496                      $errLabelSchedule.html(scheduleMessages[0]);
6497                    }
6498                  } else if (scheduleMessages.length > 0) {
6499                    reservableMessages = scheduleMessages.concat(reservableMessages);
6500                  }
6501                  $errLabel.html(reservableMessages[0] || '');
6502                  isErr = true;
6503                }
6504              }
6505
6506              if (types.indexOf('date_range') >= 0) {
6507                let startYmd = $('#' + $this.attr('data-target-start-date')).val();
6508                let endYmd = val;
6509                if(!isErr && startYmd && endYmd && startYmd > endYmd) {
6510                    $errLabel.text("開始日が最終日より後です。");
6511                    isErr = true;
6512                }
6513              }
6514              if (types.indexOf('tel') >= 0) {
6515                  if (!isErr && !isTelFormat(val)) {
6516                      //$errLabel.text("ハイフンなしで半角数字10~11文字で入力してください。");
6517                      $errLabel.text("正しい形式の電話番号を入力してください。");
6518                      isErr = true;
6519                  }
6520              }
6521              if (types.indexOf('zip') >= 0) {
6522                if (!isErr && !isZipFormat(val)) {
6523                    $errLabel.text("郵便番号を7桁で入力してください。");
6524                    isErr = true;
6525                }
6526              }
6527              if (types.indexOf('url') >= 0) {
6528                  if (!isErr && !isUrl(val)) {
6529                      $errLabel.text("正しい形式のURLを入力してください。");
6530                      isErr = true;
6531                  }
6532              }
6533              if (types.indexOf('number') >= 0) {
6534                  if (!isErr && !isNumberStr(val)) {
6535                      $errLabel.text("半角数字で入力してください。");
6536                      isErr = true;
6537                  }
6538              }
6539              if (types.indexOf('decimal') >= 0) {
6540                if (!isErr && !isDecimalStr(val)) {
6541                    $errLabel.text("数字または小数で入力してください。");
6542                    isErr = true;
6543                }
6544              }
6545              if (types.indexOf('time') >= 0) {
6546                  var currentTime = new Date();
6547                  var year = currentTime.getFullYear();
6548                  if ( !isErr && (val > year) ) {
6549                      $errLabel.text("未来日付は入力できません");
6550                      isErr = true;
6551                  }
6552              }
6553              if (types.indexOf('alphaNum') >= 0) {
6554                  if (!isErr && !isAlphaNumber(val)) {
6555                      $errLabel.text("半角英数字で入力してください。");
6556                      isErr = true;
6557                  }
6558              }
6559              if (types.indexOf('lowerAlphaNum') >= 0) {
6560                  if (!isErr && !isLowerAlphaNumber(val)) {
6561                      $errLabel.text("半角小文字英数字で入力してください。");
6562                      isErr = true;
6563                  }
6564              }
6565              if (types.indexOf('kana') >= 0) {
6566                  if (!isErr && !isKana(val)) {
6567                      $errLabel.text("全角カタカナで入力してください。");
6568                      isErr = true;
6569                  }
6570              }
6571              if (types.indexOf('newPassword') >= 0) {
6572                  if (!isErr && !isPasswordFormat(val)) {
6573                      $errLabel.text("半角8文字以上、アルファベット・数字・記号から2種以上を入力してください。");
6574                      isErr = true;
6575                  }
6576              }
6577              if (types.indexOf('postCd') >= 0) {
6578                  if (!isErr && !isPostCd(val)) {
6579                      $errLabel.text("正しい形式の郵便番号を入力してください。");
6580                      isErr = true;
6581                  }
6582              }
6583              if (types.indexOf('passwordSameChk') >= 0) {
6584                if (!isErr && !($target.val() === val)) {
6585                    $errLabel.text("同じパスワードを入力してください。");
6586                    isErr = true;
6587                }
6588              }
6589              // PAGEアカウントのパスワード専用
6590              if (types.indexOf('passwordMdfChk') >= 0) {
6591                  if (!isErr && !($("#school_password").val() === val)) {
6592                      $errLabel.text("同じパスワードを入力してください。");
6593                      isErr = true;
6594                  }
6595              }
6596              // 学校メール専用
6597              if (types.indexOf('emailMdfChk') >= 0) {
6598                  if (!isErr && !($("#school_email").val() === val)) {
6599                      $errLabel.text("同じメールアドレスを入力してください。");
6600                      isErr = true;
6601                  } 
6602              }
6603              if (types.indexOf('emailSameChk') >= 0) {
6604                  if (!isErr && !($target.val() === val)) {
6605                      $errLabel.text("同じメールアドレスを入力してください。");
6606                      isErr = true;
6607                  }
6608              }
6609              if (types.indexOf('requiredImg') >= 0) {
6610                  if (!isErr && isEmpty(val)) {
6611                      $errLabel.text($this.attr('data-message') || "画像を1枚以上アップしてください。");
6612                      isErr = true;
6613                  }
6614              }
6615              if(types.indexOf('onlyNum') >= 0){
6616                  if (!isErr && !isEmpty(val) && isNumeric(val, '1')) {
6617                      $errLabel.text("学校コードは数字だけでは指定できません。");
6618                      isErr = true;
6619                  }
6620              }
6621              if(types.indexOf('mapUrl') >= 0){
6622                if(!isErr && !isEmpty(val) && !isGoogleMapEnbed(val)){
6623                    $errLabel.text("Google Map共有の「地図を埋め込む」からコピーしてください。");
6624                    isErr = true;
6625                }else{
6626                    // 適正化
6627                    $this.val(getGoogleMapEnbedUrl(val));
6628                }
6629              }
6630
6631              // 卒業年(from/to)が逆転選択の場合
6632              if (types.indexOf('graduationYearOrderChk') >= 0) {
6633                var startYear = parseInt($('#graduate_year_start').val(), 10);
6634                var endYear   = parseInt($('#graduate_year_end').val(), 10);
6635                if (!isErr) { // まだエラーがない場合のみ
6636                  // 両方選択されている場合のみチェック
6637                  if (!isNaN(startYear) && !isNaN(endYear) && startYear > endYear) {
6638                    $errLabel.text("開始年が終了年以前になるよう選択してください。");
6639                    isErr = true;
6640                  }
6641                }
6642              }
6643
6644              if (isErr) {
6645                  isTotalErr = true;
6646              }
6647              if (isSilent) {
6648                  $errLabel.text('');
6649              }
6650          });
6651          $buttons.prop('disabled', isTotalErr);
6652          if(isTotalErr){
6653              $buttons.addClass("disabled");
6654          } else {
6655              $buttons.removeClass("disabled");
6656          }
6657      });
6658      $items.first().trigger('change', {silent: true});
6659  })(jQuery);
6660}
6661
6662/**
6663 * エラーメッセージをクリアする
6664 * @param {jQuery} $area 
6665 */
6666function clearValidationMessage($area) {
6667  $area.removeData('validation_started');
6668  $area.find('.msg_span').empty();
6669  $area.find('.err_message').empty();
6670}
6671
6672/**
6673 * POSTで送信する
6674 * @param {String} url 送信先URL
6675 * @param {array|FormData} params 送信パラメータ
6676 */
6677function post(url, params) {
6678  // パラメータ変換
6679  if (params instanceof FormData) {
6680    let param = [];
6681    for (const pair of params.entries()) {
6682      param[pair[0]] = pair[1];
6683    }
6684    params = param;
6685  }
6686
6687  let $form = $('<form/>',{action:url, method:'POST'});
6688  for(let key in params) {
6689    $form.append($('<input/>', {type: 'hidden', name: key, value: params[key]}));
6690  }
6691	$form.appendTo(document.body);
6692	$form.submit();
6693	$form.remove();
6694}
6695
6696/**
6697 * 現URLのパラメータを付与して再表示する
6698 * @param {array} putparams パラメータ
6699 */
6700function reload(putparams) {
6701  let query = location.search;
6702  let params = new URLSearchParams(query);
6703  if(putparams){
6704    for(let key in putparams) {
6705      params.set(key, putparams[key]);
6706    }
6707  }
6708  let url = location.origin + location.pathname;
6709  query = '?' + params.toString();
6710  if(query !== '?') {
6711    url += query;
6712  }
6713  location.href = url;
6714}
6715
6716/**
6717 * 現ページのURLを取得する(パラメータ無し)
6718 */
6719function getCurrentPageUrl(){
6720  let url = location.origin + location.pathname;
6721  return url;
6722}
6723
6724/**
6725 * 現URLのクエリーパラメータを取得する
6726 * @param {string} paramKey パラメータ名
6727 */
6728function getQueryParam(paramKey) {
6729  let res = '';
6730  let query = location.search;
6731  let params = new URLSearchParams(query);
6732  if(paramKey) {
6733    res = params.get(paramKey);
6734  }
6735  return res;
6736}
6737
6738/**
6739* 空であるかを検証する。
6740* @param {String} val 検証値
6741* @returns {Boolean} true:空, false:入力あり
6742*/
6743function isEmpty(val) {
6744  if (typeof val === 'undefined') {
6745      return true;
6746  }
6747  if (typeof val === 'string') {
6748      val = val.trim();
6749  }
6750
6751  return !val && val !== '0' && val !== 0;
6752}
6753
6754/**
6755* permalink形式であるかを検証する。
6756* @param {String} val 検証値
6757* @returns {Boolean} true:permalink形式, false:その他
6758*/
6759function isschoolCd(val) {
6760  var res = true;
6761  if (isNotEmpty(val)) {
6762      res = val.match(/^[a-z0-9-]+$/);
6763  }
6764  return res;
6765}
6766
6767/**
6768* email形式であるかを検証する。
6769* @param {String} val 検証値
6770* @returns {Boolean} true:email形式, false:その他
6771*/
6772function isEmail(val) {
6773  var res = true;
6774  if (isNotEmpty(val)) {
6775      res = val.match(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+(?:\.[a-zA-Z0-9-]+)*$/);
6776  }
6777  return res;
6778}
6779
6780/**
6781* email形式(csvで複数も可)であるかを検証する。
6782* @param {String} val 検証値
6783* @returns {Boolean} true:email形式, false:その他
6784*/
6785function isEmailCsv(val) {
6786  var res = true;
6787  if (isEmpty(val)) {
6788    return res;
6789  }
6790  var emailList = val.split(',');
6791  for (let i = 0; i < emailList.length; i++) {
6792    let email = emailList[i].trim();
6793    if (isEmpty(email)) {
6794      // カンマのみはNGとする
6795      res = false;
6796      break;
6797    }
6798    if (!isEmail(email)) {
6799      res = false;
6800      break;
6801    }
6802  }
6803  return res;
6804}
6805
6806/**
6807* URL形式であるかを検証する。
6808* @param {String} val 検証値
6809* @returns {Boolean} true:email形式, false:その他
6810*/
6811function isUrl(val) {
6812  var res = true;
6813  if (isNotEmpty(val)) {
6814      res = val.match(/(http|https):\/\/.+/);
6815  }
6816  return res;
6817}
6818
6819/**
6820* 電話/FAX番号形式であるかを検証する。
6821* @param {String} val 検証値
6822* @returns {Boolean} true:電話/FAX番号形式, false:その他
6823*/
6824function isTelFormat(val) {
6825  var res = true;
6826
6827  if (isNotEmpty(val)) {
6828      res = val.match(/^[0-9]{2,4}-?[0-9]{2,4}-?[0-9]{3,4}$/);
6829      //res = val.match(/^[0-9]{10,11}$/);
6830  }
6831
6832  return res;
6833}
6834
6835/**
6836* 郵便番号形式であるかを検証する。
6837* @param {String} val 検証値
6838* @returns {Boolean} true:適正な形式, false:その他
6839*/
6840function isZipFormat(val) {
6841  var res = true;
6842
6843  if (isNotEmpty(val)) {
6844      res = val.match(/^[0-9]{3}-?[0-9]{4}$/);
6845      //res = val.match(/^[0-9]{10,11}$/);
6846  }
6847
6848  return res;
6849}
6850
6851/**
6852* 半角数字のみであるかを判定する。
6853* @param {String} val 検査文字列
6854* @returns {Boolean} true:半角数字のみ, false:以外を含む
6855*/
6856function isNumberStr(val) {
6857  var res = true;
6858  if (isNotEmpty(val)) {
6859      res = val.match(/^[0-9]+$/);
6860  }
6861  return res;
6862}
6863
6864/**
6865* 半角整数または小数であるかを判定する。
6866* @param {String} val 検査文字列
6867* @returns {Boolean} true:整数/小数, false:以外を含む
6868*/
6869function isDecimalStr(val) {
6870  var res = true;
6871  if (isNotEmpty(val)) {
6872      res = val.match(/^[0-9]+\.[0-9]+$/);
6873      if(!res){
6874        res = isNumberStr(val);
6875      }
6876  }
6877  return res;
6878}
6879
6880/**
6881* 半角英数字のみであるかを判定する。
6882* @param {String} val 検査文字列
6883* @returns {Boolean} true:半角英数字のみ, false:以外を含む
6884*/
6885function isAlphaNumber(val) {
6886  var res = true;
6887  if (isNotEmpty(val)) {
6888      res = val.match(/^[A-Za-z0-9-]+$/);
6889  }
6890  return res;
6891}
6892
6893/**
6894* 半角英数字のみであるかを判定する。
6895* @param {String} val 検査文字列
6896* @returns {Boolean} true:半角英数字のみ, false:以外を含む
6897*/
6898function isLowerAlphaNumber(val) {
6899  var res = true;
6900  if (isNotEmpty(val)) {
6901      res = val.match(/^[a-z0-9-]+$/);
6902  }
6903  return res;
6904}
6905
6906/**
6907* 全角カタカナのみであるかを判定する。
6908* @param {String} val 検査文字列
6909* @returns {Boolean} true:半全角カタカナのみ, false:以外を含む
6910*/
6911function isKana(val) {
6912  var res = true;
6913  if (isNotEmpty(val)) {
6914      res = val.match(/^[ァ-ヶー\s]+$/);
6915  }
6916  return res;
6917}
6918
6919/**
6920* パスワードポリシーに合致するかを判定する。
6921* @param {String} val 検査文字列
6922* @returns {String} true:ポリシーに合致, false:その他
6923*/
6924function isPasswordFormat(val) {
6925  var res = false;
6926  var types = 0;
6927  // 文字数不足
6928  if (strLength(val) < 8) {
6929      return res;
6930  }
6931  // 不適合文字を含む
6932  if (!val.match(/^[!-~]+$/)) {
6933      return res;
6934  }
6935
6936  // 文字種数カウント
6937  if (val.match(/[A-Za-z]+/)) {
6938      val = val.replace(/[A-Za-z]+/g, '');
6939      types++;
6940  }
6941  if (val.match(/[0-9]+/)) {
6942      val = val.replace(/[0-9]+/g, '');
6943      types++;
6944  }
6945  if (!isEmpty(val)) {
6946      types++;
6947  }
6948  return types >= 2;
6949}
6950/**
6951* 郵便番号ポリシーに合致するかを判定する。
6952* @param {String} val 検査文字列
6953* @returns {Boolean} true:半全角カタカナのみ, false:以外を含む
6954*/
6955function isPostCd(val) {
6956  var res = true;
6957  if (isNotEmpty(val)) {
6958      res = val.match(/^\d{3}-?\d{4}$/);
6959  }
6960  return res;
6961}
6962
6963/**
6964 * 入力済であるかを検証する。
6965 * @param {String} val 検証値
6966 * @returns {Boolean} true:入力あり, false:空
6967 */
6968 function isNotEmpty(val) {
6969  return !isEmpty(val);
6970}
6971
6972/**
6973 * 文字数を取得する。
6974 * @param {String} str 文字列
6975 * @returns {Number} 文字数
6976 */
6977 function strLength(str) {
6978  var res = 0;
6979  if (!isEmpty(str)) {
6980      res = str.length;
6981  }
6982  return res;
6983}
6984
6985/**
6986 * 数字であるかを検証する。
6987 * @param {Number|String} num 検証値
6988 * @param {String} opt 検証モード default=整数のみ、"1"=数字に読める値、"2"=カンマ,小数点あり、"3"=小数点あり
6989 */
6990function isNumeric(num, opt){
6991  // 空白trim
6992  num = String(num).replace(/^\s+|\s+$/g, "");
6993
6994  if(typeof opt == "undefined" || opt == "1"){
6995      // すべての数字 (符号有無、カンマ区切り有無、小数点有無)
6996      var regex = /^[+\-]?(([1-9][0-9]{0,2}(,[0-9]{3})*)|[0-9]+){1}(\.[0-9]+)?$/g;
6997  }else if(opt == "2"){
6998      // 符号無し、カンマ有無、小数点有無
6999      var regex = /^(([1-9][0-9]{0,2}(,[0-9]{3})*)|[0-9]+){1}(\.[0-9]+)?$/g;
7000  }else if(opt == "3"){
7001      // 符号無し、カンマなし、小数点有無
7002      var regex = /^[0-9]+(\.[0-9]+)?$/g;
7003  }else{
7004      // 整数のみ(符号なし、カンマなし、小数点なし)
7005      var regex = /^[0-9]$/g;
7006  }
7007
7008  if( regex.test(num) ){
7009      num = num.replace(/,/g, "");
7010      return isNaN(num) ? false : true;
7011  }
7012  return false;
7013}
7014
7015/**
7016 * 埋め込み用GoogleMap URLであるかを検証する。
7017 * @param {String} val 検証値
7018 * @return {bool} true:正常, false:異なる
7019 */
7020function isGoogleMapEnbed(val) {
7021  var url = getGoogleMapEnbedUrl(val);
7022  var res = isNotEmpty(url);
7023  return res;
7024}
7025
7026/**
7027 * 埋め込み用GoogleMap URLを取得する。
7028 * @param {String} html 検証値
7029 * @return {string} 埋め込み用GoogleMap URL
7030 */
7031function getGoogleMapEnbedUrl(html) {
7032  var url = "";
7033  if(isNotEmpty(html)){
7034      var hits = html.match(/.*(https:\/\/www\.google\.com\/maps\/embed\?pb=[^"]+).*?/);
7035      if(hits && hits.length >= 2){
7036          url = hits[1];
7037      }
7038  }
7039  return url;
7040}
7041
7042/**
7043 * Twitter(X)のURLからIDを取得する。
7044 * @param {String} text 検証値
7045 * @return {string} TwitterID
7046 */
7047 function getTwitterID(text) {
7048    let id = text;
7049    if(isNotEmpty(text)){
7050      //vパラメータ
7051      let hits = text.match(/.*https:\/\/twitter\.com\/(.*)\/?/);
7052      if(hits && hits.length >= 2){
7053          id = hits[1].replace('/', '');
7054      }
7055      hits = text.match(/.*https:\/\/x\.com\/(.*)\/?/);
7056      if(hits && hits.length >= 2){
7057          id = hits[1].replace('/', '');
7058      }
7059    }
7060    return id;
7061}
7062
7063/**
7064 * FacebookのURLからIDを取得する。
7065 * @param {String} text 検証値
7066 * @return {string} FacebookID
7067 */
7068 function getFacebookID(text) {
7069    let id = text;
7070    if(isNotEmpty(text)){
7071      // pathパラメータ
7072      let hits = text.match(/.*https:\/\/www\.facebook\.com\/(.*)\/?/);
7073      if(hits && hits.length >= 2){
7074          id = hits[1].replace('/', '');
7075      }
7076      // profile.phpが表示されるようになったので追加
7077      id = id.replace("profile.php?id=", "");
7078    }
7079    return id;
7080}
7081
7082/**
7083 * InstagramのURLからIDを取得する。
7084 * @param {String} text 検証値
7085 * @return {string} InstagramID
7086 */
7087 function getInstagramID(text) {
7088    let id = text;
7089    if(isNotEmpty(text)){
7090      //vパラメータ
7091      let hits = text.match(/.*https:\/\/www\.instagram\.com\/(.*)\/?/);
7092      if(hits && hits.length >= 2){
7093          id = hits[1].replace('/', '');
7094      }
7095    }
7096    return id;
7097}
7098
7099/**
7100 * TikTokのURLからIDを取得する。
7101 * @param {String} text 検証値
7102 * @return {string} TiktokChannelID
7103 */
7104 function getTiktokChannelID(text) {
7105    let id = text;
7106    if(isNotEmpty(text)){
7107      let hits = text.match(/.*https:\/\/www\.tiktok.com\/([^\/]+)/);
7108      if(hits && hits.length >= 2){
7109          id = hits[1];
7110      }
7111    }
7112    return id;
7113}
7114
7115/**
7116 * YoutubeのURLからIDを取得する。
7117 * @param {String} text 検証値
7118 * @return {string} YoutubeChannelID
7119 */
7120 function getYoutubeChannelID(text) {
7121    let id = text;
7122    if(isNotEmpty(text)){
7123      // let hits = text.match(/.*https:\/\/www\.youtube.com\/channel\/(.*)/);
7124      // let hits = text.match(/.*https:\/\/www\.youtube.com\/c\/(.*)/);
7125      // let hits = text.match(/.*https:\/\/www\.youtube.com\/user\/(.*)/);
7126      let hits = text.match(/.*https:\/\/www\.youtube.com\/(.*)/);
7127      if(hits && hits.length >= 2){
7128          id = hits[1];
7129      }
7130    }
7131    return id;
7132}
7133
7134/**
7135 * YouTubeのURLから動画IDを取得する。
7136 * @param {String} text 検証値
7137 * @return {string} YouTube動画ID
7138 */
7139 function getYouTubeID(text) {
7140  let id = text;
7141  if(isNotEmpty(text)){
7142    //vパラメータ
7143    let hits = text.match(/.*https:\/\/[w\.]*youtube\.com.*v=([^&]+).*?/);
7144    let shortHits = text.match(/.*https:\/\/[w\.]*youtube\.com\/shorts\/([^&?\/]+).*?/);
7145    if(hits && hits.length >= 2){
7146        id = hits[1];
7147    } else if(shortHits && shortHits.length >= 2){
7148        id = shortHits[1];
7149    } else {
7150        // 短縮pathパラメータ
7151        let hits = text.match(/.*https:\/\/youtu\.be\/([^&\/]+).*?/);
7152        if(hits && hits.length >= 2){
7153            id = hits[1];
7154        }
7155    }
7156  }
7157  return id;
7158}
7159
7160/**
7161 * YouTubeコンテンツ表示IFrameを生成する
7162 * @param {string} youtubeId 
7163 * @param {string} iframeClass
7164 * @param {bool} allowFullscreen true=フルスクリーンを許可する
7165 * @return {jquery} コンテンツ表示
7166 */
7167function createYouTubeFrame(youtubeId, iframeClass, allowFullscreen, autoplay){
7168  if(!iframeClass) {
7169    iframeClass = "gallery_youtube";
7170  }
7171  let attr = '';
7172  if (allowFullscreen === true) {
7173    attr = ' allowfullscreen ';
7174  }
7175  let param = '?';
7176  if (autoplay === true) {
7177    param += '&autoplay=1&mute=1';
7178  }
7179
7180  let iframe = '<iframe class="' + iframeClass + '" src="https://www.youtube.com/embed/' + youtubeId + param + '"'
7181    + 'data-id="' + youtubeId + '" title="" frameborder="0"'
7182    + attr
7183    + ' allow="autoplay"'
7184    // + ' allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"';
7185    + '></iframe>';
7186  return $(iframe);
7187}
7188
7189/**
7190 * 画像(又は動画のサムネイル)のURLを取得する。
7191 * @param {json} image プロジェクト定義の画像情報 
7192 * @return {string} 画像URL 
7193 */
7194function getImageUrl(image) {
7195  let url = '';
7196  if (!(image['movie_type'])) {
7197      // 画像
7198      return '/wp-content' + image['path'];
7199  }
7200  if (image['movie_type'] == 'youtube') {
7201      // youtube
7202      url = 'https://img.youtube.com/vi/' + image['movie_id'] + '/mqdefault.jpg';
7203  }
7204  return url;
7205}
7206
7207/**
7208 * [1件選択用] ファイル選択
7209 * @param {string} fileId 項目ID
7210 * @param {function} beforeFunc 選択した画像表示前に呼ぶ関数
7211 * @param {function} afterFunc 選択した画像表示直後に呼ぶ関数
7212 */
7213function selectFile(fileId, beforeFunc, afterFunc) {
7214  let $image = $('#' + fileId);
7215  let $form = $image.closest('form');
7216  let types = '.pdf, .xlsx, .docx, .pptx, .zip, ' + prjutils.ImageFileTypes;
7217  let inputId = fileId + '_FILE';
7218  let $input = $('<input type="file" id="' + inputId + '" name="' + fileId + '" class="none-item" accept="' + types +'">');
7219  if ($input) {
7220      $form.find('#' + inputId).remove();
7221      $form.append($input);
7222      loadFunc = function(){
7223          if(beforeFunc){
7224              beforeFunc($input);
7225          }
7226          loadFile($input[0]);
7227          if(afterFunc){
7228              afterFunc($input);
7229          }
7230      };
7231      $input.on('change', loadFunc);
7232  }
7233  $input.click();
7234}
7235
7236/**
7237 * [(複数のうちの)1件追加用] ドロップ画像ファイル追加
7238 * @param {string} imageId 画像項目ID
7239 */
7240async function setDropImage(imageId, file) {
7241  let $image = $('#' + imageId);
7242  let $form = $image.closest('form');
7243  let types = prjutils.ImageFileTypes;
7244  let inputId = imageId + '_FILE';
7245  let $input = $('<input type="file" id="' + inputId + '" name="' + imageId + '" class="none-item" accept="' + types +'">');
7246  if ($input) {
7247    $form.find('#' + inputId).remove();
7248    $form.append($input);
7249    let dt = new DataTransfer();
7250    dt.items.add(file);
7251    $input.get(0).files = dt.files;
7252    // 選択したファイルの読込
7253    if (file) {
7254        prjutils.loadingImg(null, imageId);
7255        let fileName = file.name;
7256        try {
7257          let compressBlob = await prjutils.compressImage($input.get(0));
7258          let reader = new FileReader();
7259          reader.onload = function (e) {
7260            putImage(imageId, e.target.result, fileName);
7261            prjutils.loadingImgDel(imageId);
7262          };
7263          reader.onerror = function () {
7264            prjutils.loadingImgDel(imageId);
7265            alert_msg_html('画像を読み込めませんでした。');
7266          };
7267          reader.readAsDataURL(compressBlob);
7268        } catch (error) {
7269          prjutils.loadingImgDel(imageId);
7270          alert_msg_html(error && error.message ? error.message : '画像を変換できませんでした。');
7271        }
7272    }
7273  }
7274}
7275
7276/**
7277 * [1件選択用] ファイル選択後処理
7278 * @param {element} file 
7279 */
7280async function loadFile(file) {
7281  let $file = $(file);
7282  let fileId = $file.attr('name');
7283  // 選択したファイルの読込
7284  if (file.files && file.files[0]) {
7285    let fileName = file.files[0].name;
7286    let sizeBytes = file.files[0].size;
7287    if(isImageFileName(fileName)){
7288      loadImage(file);
7289      return;
7290    }
7291    let maxBytes = (typeof max_file_bytes !== 'undefined') ? parseInt(max_file_bytes, 10) : 5242880;
7292    if (sizeBytes > maxBytes) {
7293      let errorMsg = (typeof max_file_size_error !== 'undefined') ? max_file_size_error : 'ファイルサイズが上限を超えています。';
7294      alert_msg_html(errorMsg);
7295      return;
7296    }
7297    putFile(fileId, fileName);
7298  }
7299}
7300
7301/**
7302 * [1件選択用] ファイルを表示(保持)する。
7303 * 画像はputImage()を利用する。
7304 * @param {string} fileId 
7305 * @param {string} fileName 
7306 */
7307function putFile(fileId, fileName){
7308  let $image = $("#" + fileId);
7309  let $form = $image.closest('form');
7310  let $fileDiv = $image.closest('.file_div');
7311  let $fileSpan = $fileDiv.find('.file_name_span');
7312  let $hiddenDel = $form.find('input[type="hidden"][name="' + fileId + '_DELETE"]');
7313  $fileDiv.attr('data-type', 'file');
7314  $fileSpan.html(fileName);
7315  $hiddenDel.val('');
7316  $image.attr('src', "");
7317  $image.hide();
7318}
7319
7320/**
7321 * [1件選択用] ファイルをクリア(非選択)にする。
7322 * @param {string} fileId 
7323 */
7324function delFile(fileId) {
7325  delImage(fileId);
7326}
7327
7328/**
7329 * [1件選択用] 画像ファイル選択
7330 * @param {string} imageId 画像項目ID
7331 * @param {function} beforeFunc 選択した画像表示前に呼ぶ関数
7332 * @param {function} afterFunc 選択した画像表示直後に呼ぶ関数
7333 */
7334function selectImage(imageId, beforeFunc, afterFunc) {
7335  let $image = $('#' + imageId);
7336  let $form = $image.closest('form');
7337  let types = prjutils.ImageFileTypes;
7338  let inputId = imageId + '_FILE';
7339  let $input = $('<input type="file" id="' + inputId + '" name="' + imageId + '" class="none-item" accept="' + types +'">');
7340  if ($input) {
7341      $form.find('#' + inputId).remove();
7342      $form.append($input);
7343      loadFunc = function(){
7344          if(beforeFunc){
7345              beforeFunc($input);
7346          }
7347          loadImage($input[0]);
7348          if(afterFunc){
7349              afterFunc($input);
7350          }
7351      };
7352      $input.on('change', loadFunc);
7353  }
7354  $input.click();
7355}
7356
7357/**
7358 * [1件選択ループ用] 画像ファイル選択(表ではなくdivなどフリースタイルで一括追加する)
7359 * @param {function} newImageIdFunc input type=fileタグを生成し、サムネイル用画像項目ID
7360 * @param {function} beforeFunc 選択した画像表示前に呼ぶ関数
7361 * @param {function} afterFunc 選択した画像表示直後に呼ぶ関数
7362 */
7363function selectImageMultiple(newImageIdFunc, beforeFunc, afterFunc) {
7364  let types = prjutils.ImageFileTypes;
7365  let $input = $('<input type="file" class="none-item" accept="' + types + '" multiple>');
7366  if ($input) {
7367    $('body').append($input);
7368    let loadFunc = function(){
7369      if(beforeFunc){
7370        beforeFunc($input);
7371      }
7372      let inputElement = $input[0];
7373      let files = inputElement.files;
7374      if (files) {
7375        for(let idx = 0; idx < files.length; idx++) {
7376          let file = files[idx];
7377          let imageId = newImageIdFunc(); // ファイル選択後に都度IDを発行
7378          setDropImage(imageId, file);
7379        }
7380      }
7381      if(afterFunc){
7382        afterFunc($input);
7383      }
7384      $input.remove();
7385    };
7386    $input.on('change', loadFunc);
7387  }
7388  $input.click();
7389}
7390
7391/**
7392 * [1件選択用] 画像ファイル選択後処理
7393 * @param {element} file 
7394 */
7395async function loadImage(file) {
7396    let $file = $(file);
7397    let imageId = $file.attr('name');
7398    let $form = $file.closest('form');
7399    $form.find('#' + imageId + '_JSON, input[name="' + imageId + '_JSON"]').val('');
7400
7401    // 選択したファイルの読込
7402    if (file.files && file.files[0]) {
7403        let fileName = file.files[0].name;
7404        prjutils.loadingImg();
7405        try {
7406          let compressBlob = await prjutils.compressImage(file);
7407          let reader = new FileReader();
7408          reader.onload = function (e) {
7409              putImage(imageId, e.target.result, fileName);
7410              prjutils.loadingImgDel();
7411          };
7412          reader.onerror = function () {
7413              prjutils.loadingImgDel();
7414              alert_msg_html('画像を読み込めませんでした。');
7415          };
7416          reader.readAsDataURL(compressBlob);
7417        } catch (error) {
7418          prjutils.loadingImgDel();
7419          alert_msg_html(error && error.message ? error.message : '画像を変換できませんでした。');
7420        }
7421    }
7422    // 共通選択ダイアログを非表示
7423    prjutils.closeImageSelectionDialog();
7424}
7425
7426/**
7427 * [1件選択用] 共通画像ファイル選択後処理
7428 * @param {element} file ファイル入力項目
7429 * 
7430 */
7431// async function loadImageByJson(file, imageJson) {
7432//     let $file = $(file);
7433//     let imageId = $file.attr('name');
7434//     // 選択したファイルの読込
7435//     if (file.files && file.files[0]) {
7436//         let fileName = file.files[0].name;
7437//         prjutils.loadingImg();
7438//         let compressBlob = await prjutils.compressImage(file);
7439//         let reader = new FileReader();
7440//         reader.onload = function (e) {
7441//             putImage(imageId, e.target.result, fileName);
7442//             prjutils.loadingImgDel();
7443//         };
7444//         reader.readAsDataURL(compressBlob);
7445//     }
7446//     // 共通選択ダイアログを非表示
7447//     prjutils.closeImageSelectionDialog();
7448// }
7449
7450/**
7451 * [1件選択用] 画像を表示(保持)する。
7452 * @param {string} imageId 
7453 * @param {binary} src 
7454 * @param {string} fileName 
7455 */
7456function putImage(imageId, src, fileName){
7457    let $image = $("#" + imageId);
7458    let $form = $image.closest('form');
7459    let $fileDiv = $image.closest('.file_div');
7460    let $fileSpan = $fileDiv.find('.file_name_span');
7461    let $hiddenDel = $form.find('input[type="hidden"][name="' + imageId + '_DELETE"]');
7462    $fileDiv.attr('data-type', 'image');
7463    $fileSpan.html('');
7464    $hiddenDel.val('');
7465    $image.attr('src', src);
7466    $image.show();
7467    $form.find('#' + imageId + '_REQUIRED').val(src).trigger('change', {forceShow: true});
7468}
7469
7470/**
7471 * [1件選択用] 画像をクリア(非選択)にする。
7472 * @param {string} imageId 
7473 */
7474function delImage(imageId) {
7475    // 編集不可なら何もしない
7476    if ($('.btn-closeBadge').hasClass('disabled')) {
7477        return;
7478    }
7479
7480    let $image = $("#" + imageId);
7481    let $form = $image.closest('form');
7482    let $fileDiv = $image.closest('.file_div');
7483    let $fileSpan = $fileDiv.find('.file_name_span');
7484    let $hiddenDel = $form.find('input[type="hidden"][name="' + imageId + '_DELETE"]');
7485    $fileDiv.attr('data-type', 'image');
7486    $fileSpan.html('');
7487    $hiddenDel.val('DELETE');
7488    $image.attr('src', '');
7489    $image.hide();
7490    let inputId = imageId + '_FILE';
7491    $form.find('#' + inputId).remove();
7492    $form.find('#' + imageId + '_JSON, input[name="' + imageId + '_JSON"]').val('');
7493    $form.find('#' + imageId + '_REQUIRED').val('').trigger('change', {forceShow: true});
7494}
7495
7496/**
7497 * [複数選択用] 動画ファイル選択、表示(保持)する
7498 * 生成する行は同じtable内のclass="sample"を持つtrタグ。
7499 * @param {string}
7499 tbodyId 追加先のテーブルBODYタグID
7500 * @param {string} movieElementId 動画要素のID
7501 * @param {int} limit 上限
7502 */
7503function selectMovieTable(tbodyId, movieElementId, limit) {
7504  // 枠生成
7505  let youtubeId = $('#' + movieElementId).val();
7506  let $tbody = $('#' + tbodyId);
7507  let $trSample = $tbody.find('tr.sample');
7508  let $tr = $trSample.clone();
7509  
7510  if (limit && limit <= $tbody.find('tr:visible').length) {
7511      alert("アップロード上限は" + limit + "個です");
7512      return false;
7513  }
7514  let $input = $tr.find('input[type="file"]');
7515  $input.attr('type', 'hidden');
7516  $input.attr('data-movie', 'youtube');
7517  $input.val(youtubeId);
7518  $imageId = $tr.find('.image_id');
7519  $imageId.val('youtube::' + youtubeId);
7520
7521  $tr.removeClass('sample');
7522  $tr.find('[data-name]').each(function(){
7523      let $this = $(this);
7524      $this.attr('name', $this.data('name'));
7525  });
7526  $tbody.append($tr);
7527
7528  // 動画生成
7529  putMovieTable($tr, youtubeId);
7530  return true;
7531}
7532
7533/**
7534 * [複数選択用] 画像を表示(保持)する。
7535 * @param {jquery} $tr 表示対象行 
7536 * @param {binary} src 画像ファイル内容
7537 */
7538function putMovieTable($tr, youtubeId) {
7539  let $area = $tr.find('.img_div').first();
7540  let $filename = $tr.find('.filename');
7541  $filename.text('');
7542  let $youtube = createYouTubeFrame(youtubeId, 'icon_youtube');
7543  $area.empty();
7544  $area.append($youtube);
7545  $tr.show();
7546}
7547
7548/**
7549 * [複数選択用] 画像ファイル選択
7550 * 生成する行は同じtable内のclass="sample"を持つtrタグ。
7551 * @param {string} tbodyId 追加先のテーブルBODYタグID
7552 * @param {string} postName postパラメータ名(post値はjson書式で配列を表す) ※未使用
7553 * @param {string} postChildId 画像IDを持つ属性名(標準名 id) ※未使用
7554 * @param {string} postChildPath 画像のURLを持つ属性名(標準名 path) ※未使用
7555 * @param {string} remarks 備考配列(同じインデックスに表示)
7556 * @param {int} limit 上限
7557 */
7558 function selectImageTable(tbodyId, postName, postChildId, postChildPath, remarks, limit) {
7559    let types = prjutils.ImageFileTypes;
7560    let $tbody = $('#' + tbodyId);
7561    let $trSample = $tbody.find('tr.sample');
7562    let $tr = $trSample.clone();
7563    let $fileInput = $tr.find('input[type="file"]');
7564
7565    if (limit && limit <= $tbody.find('tr:visible').length) {
7566        alert("画像のアップロード上限は" + limit + "枚です");
7567        return false;
7568    }
7569
7570    $tr.removeClass('sample');
7571    $tr.find('[data-name]').each(function(){
7572        let $this = $(this);
7573        $this.attr('name', $this.data('name'));
7574    });
7575
7576
7577    $fileInput.attr('accept', types);
7578    $tbody.append($tr);
7579
7580    let cnt = 0;
7581    $tbody.find('.remarks').each(function(){
7582        if(remarks && cnt < remarks.length) {
7583            $(this).text(remarks[cnt++]);
7584        } else {
7585            return;
7586        }
7587    });
7588
7589    $fileInput.removeClass("none-item");
7590    $fileInput.click();
7591    $fileInput.addClass("none-item");
7592}
7593
7594/**
7595 * [複数選択用] 画像ファイル選択後処理
7596 * @param {element} file
7597 * @param {function} callbackFunc ファイル選択完了後に呼び出す関数
7598 */
7599async function loadImageTable(file, callbackFunc) {
7600    let $file = $(file);
7601    let $tr = $file.closest('tr');
7602    // 選択したファイルの読込
7603    if (file.files && file.files[0]) {
7604        prjutils.loadingImg();
7605        let fileName = file.files[0].name;
7606        try {
7607          let compressBlob = await prjutils.compressImage(file);
7608          let reader = new FileReader();
7609          reader.onload = function (e) {
7610              putImageTable($tr, e.target.result, fileName);
7611              prjutils.loadingImgDel();
7612              prjutils.closeImageSelectionDialog();
7613              if(callbackFunc){
7614                  callbackFunc();
7615              }
7616          };
7617          reader.onerror = function () {
7618              prjutils.loadingImgDel();
7619              alert_msg_html('画像を読み込めませんでした。');
7620          };
7621          reader.readAsDataURL(compressBlob);
7622        } catch (error) {
7623          prjutils.loadingImgDel();
7624          alert_msg_html(error && error.message ? error.message : '画像を変換できませんでした。');
7625        }
7626    }
7627}
7628
7629/**
7630 * [複数選択用] 画像を表示(保持)する。
7631 * @param {jquery} $tr 表示対象行 
7632 * @param {binary} src 画像ファイル内容
7633 * @param {string} fileName ファイル名
7634 */
7635function putImageTable($tr, src, fileName){
7636    let $image = $tr.find('img').first();
7637    let $filename = $tr.find('.filename');
7638    $filename.text(fileName);
7639    $image.attr('src', src);
7640    $image.show();
7641    $tr.show();
7642}
7643
7644/**
7645 * [複数選択用] 既知の画像/動画リンクを表示する。
7646 * @param {jquery} $tbody 画像選択のテーブルBODY
7647 * @param {array} image プロジェクト定義の画像情報 
7648 */
7649function createImageTr($tbody, image) {
7650  let $trSample = $tbody.find('tr.sample');
7651  let $tr = $trSample.clone();
7652  let src = binds['contenturl'] + image['path'];
7653  let fileName = image['name'];
7654  if(image['title']) {
7655    fileName = image['title'];
7656  }
7657  $tr.removeClass('sample');
7658  $tr.find('[data-name]').each(function(){
7659    let $this = $(this);
7660    $this.attr('name', $this.data('name'));
7661  });
7662
7663  if(image['movie']) {
7664    // 動画
7665    let movieId = image['movie_id'];
7666    let imageId = image['movie_type'] + '::' + movieId;
7667    $tr.find('input[name="image_id[]"]').val(imageId);
7668    $tr.find('input[type="file"]').remove();
7669    $tbody.append($tr);
7670    putMovieTable($tr, movieId);
7671  } else {
7672    // 画像
7673    $tr.find('input[name="image_id[]"]').val(image['attachment_id']);
7674    $tr.find('input[type="file"]').remove();
7675    $tbody.append($tr);
7676    putImageTable($tr, src, fileName);  
7677  }
7678
7679}
7680
7681/**
7682 * [複数選択用] 画像をクリア(非選択)にする。
7683 * @param {element} eventItem 削除対象行内の要素
7684 */
7685function delImageTable(eventItem) {
7686    let $this = $(eventItem);
7687    $this.closest('tr').remove();
7688}
7689
7690/**
7691 * 
7692 * @param {jquery} $this fileのinput項目 
7693 * @param {*} size 選択したファイルのサイズ(Bytes)
7694 * @returns 
7695 */
7696function imgCheck($this, size) {
7697    let isAccept = true;
7698    let msg = '';
7699
7700    if(!fileTypeChk('image', $this)){
7701        msg = img_exten_error;
7702        isAccept = false;
7703    }
7704    if (!fileSizeChk(size, $this)) {
7705        msg = all_img_size_error;
7706        msg += '(最大 ' + (size / 1024) + 'KB)';
7707        isAccept = false;
7708    }
7709    if (isAccept === false) {
7710        $this.value = '';
7711        alert_msg(msg, null);
7712    }
7713
7714    return isAccept;
7715}
7716
7717/**
7718 * imgタグで空ソースの場合非表示/ソースありを表示に切り替える。
7719 * 対象:img.nullable
7720 */
7721function refreshNullableImage() {
7722    $('img.nullable').each(function(){
7723        $this = $(this);
7724        let src = $this.attr('src');
7725        if(src){
7726            $this.show();
7727        } else {
7728            $this.hide();
7729        }
7730    });
7731}
7732
7733/**
7734 * 画像ファイルであるかを検証する。
7735 * @param fileName ファイル名
7736 * @return true:画像ファイル名
7737 */
7738function isImageFileName(fileName) {
7739  let imageSuffix = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg', '.heic', '.heif'];
7740  let lowerFileName = fileName.toLowerCase();
7741  for(let suffix of imageSuffix) {
7742    if(lowerFileName.endsWith(suffix)) {
7743      return true;
7744    }
7745  }
7746  return false;
7747}
7748
7749/**
7750 * [1件選択用] CSVファイル選択
7751 * @param {string} fileId 画面項目ID(ダミーのinput file)
7752 * @param {function} afterFunc ファイル選択後に呼ぶ関数
7753 */
7754function selectCsv(fileId, afterFunc) {
7755  let $file = $('#' + fileId);
7756  let $form = $file.closest('form');
7757  let types = '.csv';
7758  // let inputId = fileId + '_FILE';
7759  let inputId = fileId;
7760  let $input = $('<input type="file" id="' + inputId + '" name="' + fileId + '" class="none-item" accept="' + types +'">');
7761  if ($input) {
7762    $file.remove();
7763    $form.append($input);
7764    loadFunc = function(){
7765      if(afterFunc){
7766        afterFunc($input);
7767      }
7768    };
7769    $input.on('change', loadFunc);
7770  }
7771  $input.click();
7772}
7773
7774/**
7775 * 全角英数字を半角に変換する。
7776 * @param {string} text 変換前文字列 
7777 */
7778function toHarfNumAlphabet(text) {
7779  return text.replace(/[A-Za-z0-9]/g, function(s) {
7780    return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
7781  });
7782}
7783
7784//金額のカンマ区切り
7785document.querySelectorAll('.amount_cell').forEach(el => {
7786  const text = el.textContent.trim();
7787  if (text === '' || text === '0' || text === '0円') {
7788    el.textContent = '';
7789    return;
7790  }
7791  const cleanText = text.replace(/[,円]/g, '');
7792  const rawValue = Number(el.dataset.amount || cleanText);
7793  if (!isNaN(rawValue) && rawValue > 0) {
7794    el.textContent = rawValue.toLocaleString('ja-JP') + '円';
7795  } else {
7796    el.textContent = '';
7797  }
7798});

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.