Files
edu/skin/insight.php
T

1538 lines
55 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../bbs/auth.php';
edu_require_login();
?>
<!doctype html>
<html lang="ko">
<head>
<?php include(__DIR__ . "/_include/_head.php") ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@700&display=swap" rel="stylesheet">
</head>
<?php
$SET_PREFIX = 'I'; //L=리더십(기본값), I=인사이트
$activeCate = isset($_GET['cate']) ? trim($_GET['cate']) : '';
require_once __DIR__ . '/../bbs/category_init_data.php';
?>
<body>
<div class="wrap leadership">
<?php include(__DIR__ . "/_include/_header.php") ?>
<!-- container -->
<div class="container">
<!-- editor's pick -->
<section class="leadership-hero">
<div class="leadership-inner hero-top hero-breadcrumb">
<ul class="breadcrumb" aria-label="breadcrumb">
<li><a href="./index.php">홈</a></li>
<li><a href="./insight.php">인사이트</a></li>
<li><span class="current"></span></li>
</ul>
</div>
<!-- editor's pick -->
<!-- 인사이트 고정 배너 3종: PC 1442×460 / 모바일 360×234 -->
<div class="hero-banner">
<div class="swiper hero-swiper">
<div class="swiper-wrapper">
<?php if (!empty($array_insight_banner)): ?>
<?php foreach ($array_insight_banner as $banner): ?>
<div class="swiper-slide">
<div class="banner-title">
<picture>
<source media="(min-width: 1025px)" srcset="<?= htmlspecialchars($banner['banner_title_pc'], ENT_QUOTES, 'UTF-8') ?>">
<source media="(max-width: 1024px)" srcset="<?= htmlspecialchars($banner['banner_title_mo'], ENT_QUOTES, 'UTF-8') ?>">
<img src="<?= htmlspecialchars($banner['banner_title_pc'], ENT_QUOTES, 'UTF-8') ?>" alt="<?= htmlspecialchars($banner['banner_title_alt'], ENT_QUOTES, 'UTF-8') ?>">
</picture>
</div>
<div class="banner-grid">
<div class="banner-left">
<?php if (!empty($banner['is_top_learner'])): ?>
<div class="profile-box">
<?php if (!empty($banner['offer_member_name'])): ?>
<div class="profile-img">
<span class="profile-crown">
<img src="/img/insight/crown.svg" alt="">
</span>
<span class="img-box" style="display:block; overflow:hidden; border-radius:50%;">
<img src="<?= htmlspecialchars($banner['offer_profile_image'], ENT_QUOTES, 'UTF-8') ?>" alt="<?= htmlspecialchars($banner['offer_member_name'], ENT_QUOTES, 'UTF-8') ?>" onerror="this.src='/img/ico/ico_user.svg'" style="display:block; width:100%; height:100%; object-fit:cover; border-radius:50%;">
</span>
</div>
<?php endif; ?>
<div class="profile-info">
<p class="profile-name">
<strong class="name"><?= htmlspecialchars(trim($banner['offer_member_name'] . ' ' . $banner['offer_rank_name']), ENT_QUOTES, 'UTF-8') ?></strong>
<span class="department"><?= htmlspecialchars($banner['offer_dept_name'], ENT_QUOTES, 'UTF-8') ?></span>
</p>
<span class="label"><i class="ico-medal"></i>우수학습자</span>
</div>
</div>
<?php endif; ?>
<dl>
<dt>
<?= htmlspecialchars($banner['description1'], ENT_QUOTES, 'UTF-8') ?><br>
<?= htmlspecialchars($banner['description2'], ENT_QUOTES, 'UTF-8') ?>
</dt>
<dd><?= nl2br(htmlspecialchars($banner['description3'], ENT_QUOTES, 'UTF-8')) ?></dd>
</dl>
</div>
<div class="banner-right">
<div class="banner-img">
<a href="#"
class="banner-video-trigger"
data-content-id="<?= htmlspecialchars($banner['content_id'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
data-content-url="<?= htmlspecialchars($banner['content_url'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
data-title="<?= htmlspecialchars($banner['title'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
data-description="<?= htmlspecialchars($banner['description3'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
data-category-name="<?= htmlspecialchars($banner['category_name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
data-sub-category="<?= htmlspecialchars($banner['sub_category_name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>"
data-watch-tm="0"
data-content-tm="0">
<img src="<?= htmlspecialchars($banner['thumbnail'], ENT_QUOTES, 'UTF-8') ?>" alt="<?= htmlspecialchars($banner['title'], ENT_QUOTES, 'UTF-8') ?>">
</a>
</div>
<?php if (!empty($banner['is_top_learner'])): ?>
<div class="profile-info">
<p class="profile-name">
<strong class="name"><?= htmlspecialchars(trim($banner['offer_member_name'] . ' ' . $banner['offer_rank_name']), ENT_QUOTES, 'UTF-8') ?></strong>
<span class="department"><?= htmlspecialchars($banner['offer_dept_name'], ENT_QUOTES, 'UTF-8') ?></span>
</p>
<span class="label"><i class="ico-medal"></i>우수학습자</span>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<?php foreach ($array_banner_img as $i => $bn): ?>
<div class="swiper-slide">
<div class="banner-img">
<picture>
<source media="(min-width: 1025px)" srcset="<?= $bn['normal'] ?>">
<source media="(max-width: 1024px)" srcset="<?= $bn['mobile'] ?>">
<img src="<?= $bn['normal'] ?>" alt="<?= $bn['title'] ?>">
</picture>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<div class="swiper-pagination hero-pagination" aria-hidden="true"></div>
</div>
</section>
<!-- Tabs -->
<div class="leadership-tabs" aria-label="카테고리">
<div class="leadership-inner">
<div class="leadership-tabs-list" role="tablist" aria-label="카테고리 탭">
<?php foreach ($array_tab_info as $i => $ti):
$isActive = ($activeCate !== '') ? ($ti['base_code'] === $activeCate) : ($i === 0);
?>
<button class="leadership-tab <?php if($isActive){?>is-active<?php }?>" type="button" role="tab" aria-selected="<?php if($isActive){?>true<?php }else{?>false<?php }?> " data-cate="<?= $ti['base_code'] ?>">
<span class="tab-icon"><img src="<?= $ti['src'] ?>" alt="" /></span>
<span class="tab-text"><?= $ti['code_name'] ?></span>
</button>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- video list -->
<section class="leadership-video-list">
<div class="leadership-inner">
<div class="list-head">
<span class="total">전체 <em></em></span>
<div class="list-options">
<div class="select-wrap">
<select class="select-sort" title="정렬">
<option value="view" selected>시청수</option>
<option value="latest" >업데이트</option>
<!-- <option value="seen">내가본컨텐츠</option>
<option value="unseen">안본컨텐츠</option> -->
</select>
</div>
</div>
</div>
<!-- 탭선택에 따른 동적 컨텐츠영역 -->
<ul class="video-grid" id="video-list"></ul>
</div>
</section>
</div>
<!-- // container -->
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Hero Swiper
new Swiper('.hero-swiper', {
loop: true,
autoplay: {
delay: 4000,
disableOnInteraction: false,
},
speed: 600,
pagination: {
el: '.hero-pagination',
clickable: true,
},
});
// Tabs
const tabs = Array.from(document.querySelectorAll('.leadership-tab'));
if (!tabs.length) return;
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
tabs.forEach(function (t) {
t.classList.remove('is-active');
t.setAttribute('aria-selected', 'false');
});
let currentText = $(this).find('.tab-text').text();
console.log('tabs');
console.log(currentText);
$('.current').text(currentText);
tab.classList.add('is-active');
tab.setAttribute('aria-selected', 'true');
});
});
});
</script>
<script>
$( document ).ready(function() {
let firstTabText = $('.leadership-tab.is-active').find('.tab-text').text();
if($('.current').text() == "" && firstTabText != ""){
$('.current').text(firstTabText);
}
});
$(function () {
let page = 1;
let loading = false;
let lastPage = false;
let requestSeq = 0;
var urlParams = new URLSearchParams(window.location.search);
var urlCate = urlParams.get('cate') || '';
if (urlCate) {
$('.leadership-tab').removeClass('is-active').attr('aria-selected', 'false');
var $matchTab = $('.leadership-tab[data-cate="' + urlCate + '"]');
if ($matchTab.length) {
$matchTab.addClass('is-active').attr('aria-selected', 'true');
}
}
let category = urlCate || $('.leadership-tab.is-active').data('cate') || 'CA200<?= $SET_PREFIX ?>01';
let sort = $('.select-sort').val() || 'latest';
loadVideos();
$('.leadership-tab').on('click', function () {
if (loading) return;
$('.leadership-tab').removeClass('is-active').attr('aria-selected', 'false');
$(this).addClass('is-active').attr('aria-selected', 'true');
category = $(this).data('cate');
resetList(true);
loadVideos();
});
$('.select-sort').on('change', function () {
if (loading) return;
sort = $(this).val();
resetList(true);
loadVideos();
});
$('.container').on('scroll', function () {
if (loading || lastPage) return;
const scrollTop = this.scrollTop;
const windowHeight = this.clientHeight;
const docHeight = this.scrollHeight;
if (scrollTop + windowHeight >= docHeight - 100) {
page++;
loadVideos();
}
});
function resetList(moveTop) {
page = 1;
lastPage = false;
$('#video-list').empty();
if (moveTop) {
$('.container').scrollTop(0);
}
}
function setLoading(isLoading) {
loading = isLoading;
}
function normalizeBookmarkMarkup() {
// 서버 렌더 템플릿의 인라인 onclick(event.stopPropagation()) 제거
$('#video-list .bookmark').removeAttr('onclick');
}
function applyKeywordDesc() {
$('#video-list .video-item').each(function () {
var $item = $(this);
var rawKeywords = String(
$item.attr('data-keywords')
|| $item.data('keywords')
|| $item.attr('data-keyword')
|| $item.data('keyword')
|| ''
).trim();
if (!rawKeywords) return;
var keywordText = rawKeywords
.split(/[\,\|]/)
.map(function (kw) { return String(kw || '').trim(); })
.filter(Boolean)
.map(function (kw) { return kw.indexOf('#') === 0 ? kw : ('#' + kw); })
.join(' ');
if (!keywordText) return;
var $desc = $item.find('.item-desc').first();
if ($desc.length) {
$desc.text(keywordText);
}
});
}
function loadVideos() {
requestSeq++;
const currentRequestSeq = requestSeq;
setLoading(true);
$.ajax({
url: '/ajax/get_video_list.php?SET_PREFIX=<?= $SET_PREFIX ?>',
type: 'GET',
dataType: 'json',
cache: false,
data: {
category: category,
sort: sort,
page: page,
limit: 'all'
},
success: function (res) {
if (currentRequestSeq !== requestSeq) return;
if (!res || res.success !== true) {
if (page > 1) page--;
return;
}
const html = $.trim(res.html || '');
const totalCount = parseInt(res.total_count, 10) || 0;
$('.total em').text(totalCount);
if (page === 1) {
$('#video-list').html(html);
normalizeBookmarkMarkup();
applyKeywordDesc();
if (html === '') {
lastPage = true;
$('#video-list').html(
'<li class="video-item video-empty">' +
'<div class="item-info">' +
'<strong class="item-title">등록된 콘텐츠가 없습니다.</strong>' +
'</div>' +
'</li>'
);
}
} else {
if (html === '') {
lastPage = true;
page--;
} else {
$('#video-list').append(html);
normalizeBookmarkMarkup();
applyKeywordDesc();
}
}
},
error: function () {
if (page > 1) page--;
},
complete: function () {
if (currentRequestSeq === requestSeq) {
setLoading(false);
}
}
});
}
});
</script>
<!-- ============================================ -->
<!-- 영상 모달 댓글 스타일 (온보딩 동일) -->
<!-- ============================================ -->
<style>
/* #insightVideoModal .comment-list { list-style: none; margin: 0; padding: 0; }
#insightVideoModal .comment-list li { padding: 0; border-bottom: none; }
#insightVideoModal .comment-list li.empty-comment { padding: 20px; text-align: center; color: rgba(255,255,255,0.4); font-size: 13px; }
#insightVideoModal .comment-list .comment-info { display: flex; align-items: flex-start; }
#insightVideoModal .comment-list .comment-info .photo { flex-shrink: 0; margin-right: 6px; display: flex; align-items: center; }
#insightVideoModal .comment-list .comment-info .photo img { width: 20px; height: 20px; border-radius: 50%; }
#insightVideoModal .comment-list .comment-info .user-comment { flex: 1; min-width: 0; display: flex; align-items: center; }
#insightVideoModal .comment-list .comment-info .user-comment .user-name { font-size: 13px; color: #ccc; white-space: nowrap; flex-shrink: 0; margin-right: 8px; }
#insightVideoModal .comment-list .comment-info .user-comment .user-text { flex: 1; background: transparent; border: none; color: #fff; font-size: 13px; line-height: 1.4; resize: none; padding: 10px 0 0 0; word-break: break-word; font-family: inherit; }
#insightVideoModal .comment-list .comment-info .user-comment .user-text:disabled { opacity: 1; cursor: default; padding-top: 10px;}
#insightVideoModal .comment-list .comment-info .user-comment .user-text:not(:disabled) { border: 1px solid #555; border-radius: 4px; padding: 4px 8px; background: #2a2a2a; }
#insightVideoModal .comment-list .comment-info .actions { display: flex; gap: 4px; align-items: center; flex-shrink: 0; margin-left: 8px; align-self: center; }
#insightVideoModal .comment-list .comment-info .actions button { padding: 3px 8px; font-size: 12px; border: 1px solid #555; border-radius: 3px; background: #333; color: #ddd; cursor: pointer; white-space: nowrap; }
#insightVideoModal .comment-list .comment-info .actions button:hover { background: #555; color: #fff; }
#insightVideoModal .comment-list li.editing .user-text { border: 1px solid #00ffcc !important; } */
/* 영상 리스트 키워드 + 조회수 */
.item-desc-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
gap: 8px;
}
.item-desc {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-view-count {
margin-top: 10px;
flex-shrink: 0;
white-space: nowrap;
font-size: 14px;
color: rgba(7, 7, 7, 0.6);
}
/* 관련영상 목록 스크롤 */
#insightVideoModal .video-side .video-list {
overflow: hidden;
}
#insightVideoModal #recommendedList {
max-height: none;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
padding-right: 4px;
}
#insightVideoModal #recommendedList::-webkit-scrollbar {
width: 6px;
}
#insightVideoModal #recommendedList::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.35);
border-radius: 999px;
}
#insightVideoModal #recommendedList::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.08);
}
#insightVideoModal .comment-wrap {
display: flex;
flex-direction: column;
min-height: 220px;
box-sizing: border-box;
}
#insightVideoModal .comment-resizer {
touch-action: none;
user-select: none;
}
#insightVideoModal .comment-list-wrap {
flex: 1;
min-height: 0;
overflow-y: auto;
}
#insightVideoModal .comment-box {
flex-shrink: 0;
}
/* 1024px 이하: 댓글 패널(슬라이드업) 토글 */
@media (max-width: 1024px) {
#insightVideoModal .comment-wrap {
height: min(48vh, 420px);
}
#insightVideoModal .comment-wrap {
position: fixed;
left: 0;
right: 0;
bottom: 0;
width: 100%;
max-height: min(70vh, 520px);
z-index: 1002;
transform: translateY(100%);
transition: transform 220ms ease;
}
#insightVideoModal.is-comment-open .comment-wrap {
transform: translateY(0);
}
}
</style>
<!-- ============================================ -->
<!-- 영상 모달 (video.php 구조 참고) -->
<!-- ============================================ -->
<div class="modal video" id="insightVideoModal" style="display:none">
<div class="modal-content">
<div class="modal-body">
<div class="video-contents">
<div class="video-area">
<div class="video-box">
<div id="videoPlayer"></div>
</div>
</div>
<div class="video-info">
<div class="tit-box">
<div class="meta">
<span id="modalCategoryName"></span>
<em id="modalSubCategory"></em>
</div>
<h3 id="modalTitle"></h3>
<div class="tit-right">
<button class="btn-comment"><i class="ico-comment"></i>0</button>
<label class="bookmark" for="insight-bookmark-chk">
<input id="insight-bookmark-chk" type="checkbox" value="" />
<span>북마크</span>
</label>
</div>
</div>
<div class="desc" id="modalDesc"></div>
</div>
</div>
<div class="video-side">
<div class="video-header">
<h5 class="tit">관련영상</h5>
<span class="badge" id="modalBadge">인사이트</span>
<span class="close">&times;</span>
</div>
<div class="video-list">
<ul id="recommendedList">
<!-- JS로 동적 렌더링 -->
</ul>
</div>
<div class="comment-wrap">
<!-- 🔥 드래그 리사이저 추가 -->
<div class="comment-resizer">
<div class="resizer-handle"></div>
</div>
<!-- 댓글 목록 -->
<div class="comment-list-wrap">
<ul class="comment-list">
<li class="empty-comment" style="padding:20px; text-align:center; color:rgba(255,255,255,0.4); font-size:13px;">작성된 댓글이 없습니다.</li>
</ul>
</div>
<!-- 댓글 입력 -->
<div class="comment-box">
<textarea placeholder="댓글을 작성해주세요"></textarea>
<div class="btn-area">
<button class="btn-cancel" disabled>취소</button>
<button class="btn-save" disabled>등록</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ============================================ -->
<!-- 영상 모달 JS (클릭 → 모달 열기 / 추천영상 / 댓글) -->
<!-- ============================================ -->
<script>
$(function () {
var API_BASE = '/bbs/api';
var $modal = $('#insightVideoModal');
var currentContentId = null;
var commentToggleCleanup = null;
// ── YouTube IFrame API 시청 추적 변수 ──
var ytPlayer = null;
var trackingInterval = null;
var lastSaveTime = 0;
var lastCurrentTime = 0;
// YouTube IFrame API 스크립트 로드
if (!document.getElementById('youtube-iframe-api')) {
var ytScript = document.createElement('script');
ytScript.id = 'youtube-iframe-api';
ytScript.src = 'https://www.youtube.com/iframe_api';
ytScript.async = true;
document.head.appendChild(ytScript);
}
// ── YouTube 비디오 ID 추출 ──
function extractVideoId(url) {
if (!url) return '';
var videoId = url;
if (url.indexOf('youtube.com/watch') > -1) {
try { videoId = new URL(url).searchParams.get('v') || url; } catch (e) {}
} else if (url.indexOf('youtu.be/') > -1) {
videoId = url.split('youtu.be/')[1].split(/[?&#]/)[0];
} else if (url.indexOf('youtube.com/embed/') > -1) {
videoId = url.split('youtube.com/embed/')[1].split(/[?&#]/)[0];
}
if (videoId && videoId.indexOf('/') === -1 && videoId.indexOf('.') === -1) {
return videoId;
}
return '';
}
// ── YT.Player 생성 ──
function initYTPlayer(videoId, startSeconds) {
destroyYTPlayer();
startSeconds = Math.floor(startSeconds || 0);
var createPlayer = function () {
ytPlayer = new YT.Player('videoPlayer', {
height: '100%',
width: '100%',
videoId: videoId,
playerVars: {
autoplay: 1,
controls: 1,
rel: 0,
modestbranding: 1,
enablejsapi: 1,
start: startSeconds
},
events: {
'onReady': function (event) {
if (event.target && typeof event.target.playVideo === 'function') {
event.target.playVideo();
}
},
'onStateChange': function (event) {
handlePlayerStateChange(event);
},
'onError': function (event) {
console.error('[Insight] Player Error:', event.data);
}
}
});
};
if (typeof YT !== 'undefined' && YT.Player && typeof YT.Player === 'function') {
createPlayer();
} else {
var checkYt = setInterval(function () {
if (typeof YT !== 'undefined' && YT.Player && typeof YT.Player === 'function') {
clearInterval(checkYt);
createPlayer();
}
}, 500);
setTimeout(function () { clearInterval(checkYt); }, 10000);
}
}
// ── 플레이어 파괴 ──
function destroyYTPlayer() {
stopTracking();
if (ytPlayer && typeof ytPlayer.destroy === 'function') {
try { ytPlayer.destroy(); } catch (e) {}
}
ytPlayer = null;
var $box = $modal.find('.video-box');
if (!$box.find('#videoPlayer').length) {
$box.html('<div id="videoPlayer"></div>');
}
}
// ── 플레이어 상태 변경 핸들러 (VideoModalBase 패턴) ──
function handlePlayerStateChange(event) {
if (event.data === YT.PlayerState.PLAYING) {
startTracking();
} else {
stopTracking();
if (event.data === YT.PlayerState.PAUSED || event.data === YT.PlayerState.ENDED) {
saveLearningProgress(Math.floor(lastSaveTime), 'N');
lastSaveTime = 0;
}
}
}
// ── 시청 추적 시작 (1초 주기) ──
function startTracking() {
if (trackingInterval) return;
lastSaveTime = 0;
trackingInterval = setInterval(function () {
if (!ytPlayer || typeof ytPlayer.getCurrentTime !== 'function') return;
var playerState = (typeof ytPlayer.getPlayerState === 'function')
? ytPlayer.getPlayerState() : null;
if (playerState !== YT.PlayerState.PLAYING) return;
var currentTime = ytPlayer.getCurrentTime();
var diff = currentTime - lastCurrentTime;
if (Math.abs(diff) <= 5) {
lastSaveTime += 1;
}
lastCurrentTime = currentTime;
// 5초마다 서버에 heartbeat 전송
if (lastSaveTime >= 30) {
saveLearningProgress(lastSaveTime);
lastSaveTime = 0;
}
}, 1000);
}
// ── 시청 추적 중지 ──
function stopTracking() {
if (trackingInterval) {
clearInterval(trackingInterval);
trackingInterval = null;
}
}
// ── 서버에 학습 진행상황 저장 (save_learning.php) ──
function saveLearningProgress(heartbeatSeconds, isWatching) {
if (!ytPlayer || !currentContentId) return;
if (typeof ytPlayer.getCurrentTime !== 'function') return;
var currentTime = Math.floor(ytPlayer.getCurrentTime());
var duration = Math.floor(ytPlayer.getDuration());
var incrementalWatch = Math.max(0, Math.floor(heartbeatSeconds || 0));
var params = new URLSearchParams({
content_id: String(currentContentId),
watch_tm: String(Math.max(0, currentTime)),
content_tm: String(Math.max(0, duration)),
all_tm_increment: String(incrementalWatch),
is_watching: isWatching === 'N' ? 'N' : 'Y'
});
fetch(API_BASE + '/save_learning.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: params.toString(),
keepalive: true
}).then(function (r) { return r.json(); }).then(function (result) {
if (!result.success) {
console.error('[Insight] Save Learning Failed:', result.message);
}
}).catch(function (e) {
console.error('[Insight] Save Learning Error:', e);
});
}
// ── 영상 카드 클릭 → 모달 열기 ──
$(document).on('click', '#video-list .video-item .card-link', function (e) {
if ($(e.target).closest('.bookmark').length) {
e.preventDefault();
e.stopPropagation();
return;
}
e.preventDefault();
var $item = $(this).closest('.video-item');
var contentId = $item.data('content-id') || $item.attr('data-content-id') || '';
var videoUrl = $item.data('content-url') || $item.attr('data-content-url') || '';
var title = $item.data('title') || $item.find('.item-title').text().trim() || '';
var desc = $item.data('description') || '';
var catName = $item.data('category-name') || $item.attr('data-category-name') || $item.find('.item-category').text().trim() || '인사이트';
var subCat = $item.data('sub-category') || $item.attr('data-sub-category') || '';
var watchTm = parseInt($item.data('watch-tm') || $item.attr('data-watch-tm') || '0', 10);
var contentTm = parseInt($item.data('content-tm') || $item.attr('data-content-tm') || '0', 10);
var isBookmarked = $item.find('.bookmark input[type="checkbox"]').is(':checked');
if (!contentId) {
console.warn('[VideoModal] content-id가 없습니다.');
return;
}
openModal({
contentId: contentId,
videoUrl: videoUrl,
title: title,
description: desc,
categoryName: catName,
subCategory: subCat,
watchTm: watchTm,
contentTm: contentTm,
bookmark: isBookmarked
});
});
// ── 상단 배너 클릭 → 동일 모달 열기 ──
$(document).on('click', '.hero-banner .banner-video-trigger', function (e) {
e.preventDefault();
var $banner = $(this);
var contentId = String($banner.data('content-id') || $banner.attr('data-content-id') || '');
var videoUrl = String($banner.data('content-url') || $banner.attr('data-content-url') || '');
var title = String($banner.data('title') || $banner.attr('data-title') || '').trim();
var desc = String($banner.data('description') || $banner.attr('data-description') || '');
var catName = String($banner.data('category-name') || $banner.attr('data-category-name') || '인사이트');
var subCat = String($banner.data('sub-category') || $banner.attr('data-sub-category') || '');
var watchTm = parseInt($banner.data('watch-tm') || $banner.attr('data-watch-tm') || '0', 10) || 0;
var contentTm = parseInt($banner.data('content-tm') || $banner.attr('data-content-tm') || '0', 10) || 0;
// 동일 content_id 카드가 리스트에 있으면 해당 메타(카테고리/그룹/설명/진도)를 우선 사용
var $card = $('#video-list .video-item[data-content-id="' + contentId + '"]');
if ($card.length) {
videoUrl = String($card.data('content-url') || $card.attr('data-content-url') || videoUrl);
title = String($card.data('title') || $card.attr('data-title') || title).trim();
desc = String($card.data('description') || $card.attr('data-description') || desc);
catName = String($card.data('category-name') || $card.attr('data-category-name') || catName);
subCat = String($card.data('sub-category') || $card.attr('data-sub-category') || subCat);
watchTm = parseInt($card.data('watch-tm') || $card.attr('data-watch-tm') || String(watchTm), 10) || 0;
contentTm = parseInt($card.data('content-tm') || $card.attr('data-content-tm') || String(contentTm), 10) || 0;
}
if (!contentId || !videoUrl) {
console.warn('[Insight Banner] content_id 또는 video_url이 없습니다.');
return;
}
var isBookmarked = $card.length ? $card.find('.bookmark input[type="checkbox"]').is(':checked') : undefined;
openModal({
contentId: contentId,
videoUrl: videoUrl,
title: title,
description: desc,
categoryName: catName,
subCategory: subCat,
watchTm: watchTm,
contentTm: contentTm,
bookmark: isBookmarked
});
});
// ── 리스트 카드 북마크 토글/저장 ──
function saveListBookmark($chk, contentId, isActive) {
$chk.prop('disabled', true);
$.ajax({
url: '/bbs/api/save_wishlist.php',
type: 'POST',
data: { content_id: contentId, is_active: isActive },
dataType: 'json',
success: function (res) {
if (!res || res.success !== true) {
$chk.prop('checked', isActive !== '1');
return;
}
if (currentContentId && String(currentContentId) === String(contentId)) {
$modal.find('.bookmark input[type="checkbox"]').prop('checked', isActive === '1');
}
},
error: function () {
$chk.prop('checked', isActive !== '1');
},
complete: function () {
$chk.prop('disabled', false);
}
});
}
$(document).on('click', '#video-list .bookmark', function (e) {
if ($(e.target).is('input[type="checkbox"]')) {
e.stopPropagation();
return;
}
e.preventDefault();
e.stopPropagation();
var $item = $(this).closest('.video-item');
var contentId = String($item.data('content-id') || $item.attr('data-content-id') || '');
if (!contentId) return;
var $chk = $(this).find('input[type="checkbox"]');
if (!$chk.length || $chk.prop('disabled')) return;
$chk.prop('checked', !$chk.prop('checked')).trigger('change');
});
$(document).on('change', '#video-list .bookmark input[type="checkbox"]', function (e) {
e.stopPropagation();
var $chk = $(this);
var $item = $chk.closest('.video-item');
var contentId = String($item.data('content-id') || $item.attr('data-content-id') || '');
if (!contentId) return;
var isActive = $chk.is(':checked') ? '1' : '0';
saveListBookmark($chk, contentId, isActive);
});
function adjustRecommendedListHeight() {
if (!$modal.is(':visible')) return;
var $videoListWrap = $modal.find('.video-list');
var $list = $modal.find('#recommendedList');
var $commentWrap = $modal.find('.comment-wrap');
if (!$videoListWrap.length || !$list.length || !$commentWrap.length) return;
var listTop = $videoListWrap.offset().top;
var commentTop = $commentWrap.offset().top;
if (!Number.isFinite(listTop) || !Number.isFinite(commentTop)) return;
var availableHeight = Math.floor(commentTop - listTop - 8);
if (availableHeight < 120) availableHeight = 120;
$list.css('max-height', availableHeight + 'px');
}
// ── 인사이트 댓글 패널 토글 + 드래그 리사이즈 ──
function setupCommentToggle() {
// 중복 바인딩 방지
if (typeof commentToggleCleanup === 'function') return;
var $btnComment = $modal.find('.btn-comment').first();
var $videoSide = $modal.find('.video-side').first();
var $commentWrap = $modal.find('.comment-wrap').first();
if (!$btnComment.length || !$videoSide.length || !$commentWrap.length) return;
var mediaQuery = window.matchMedia('(max-width: 1024px)');
var dragState = null;
function getViewportHeight() {
if (window.visualViewport && window.visualViewport.height) {
return window.visualViewport.height;
}
return window.innerHeight || document.documentElement.clientHeight || 0;
}
function getMinCommentHeight() {
return 220;
}
function getMaxCommentHeight() {
if (!isMobileView()) {
var videoSideHeight = $videoSide.innerHeight() || 0;
var videoHeaderHeight = $videoSide.find('.video-header').outerHeight(true) || 0;
if (videoSideHeight > videoHeaderHeight) {
return Math.max(getMinCommentHeight(), videoSideHeight - videoHeaderHeight - 40);
}
}
return Math.min(Math.floor(getViewportHeight() * 0.7), 520);
}
function clampCommentHeight(height) {
var minHeight = getMinCommentHeight();
var maxHeight = getMaxCommentHeight();
if (maxHeight < minHeight) {
minHeight = Math.max(160, maxHeight);
}
return Math.max(minHeight, Math.min(height, maxHeight));
}
function getDefaultCommentHeight() {
return clampCommentHeight(Math.floor(getViewportHeight() * 0.48));
}
function getPointerY(event) {
var originalEvent = event && event.originalEvent ? event.originalEvent : event;
if (originalEvent && originalEvent.touches && originalEvent.touches.length) {
return originalEvent.touches[0].clientY;
}
if (originalEvent && originalEvent.changedTouches && originalEvent.changedTouches.length) {
return originalEvent.changedTouches[0].clientY;
}
return originalEvent ? originalEvent.clientY : NaN;
}
function applyCommentHeight(height) {
var nextHeight = clampCommentHeight(Math.round(height));
$commentWrap.css('height', nextHeight + 'px');
adjustRecommendedListHeight();
}
function isMobileView() {
return mediaQuery.matches;
}
function openComment() {
applyCommentHeight(parseFloat($commentWrap[0].style.height) || getDefaultCommentHeight());
$modal.addClass('is-comment-open');
}
function closeComment() {
$modal.removeClass('is-comment-open');
}
function clearMobileStyles() {
var el = $commentWrap.get(0);
if (el && el.style) el.style.cssText = '';
var listWrap = $commentWrap.find('.comment-list-wrap').get(0);
if (listWrap && listWrap.style) listWrap.style.cssText = '';
}
function restoreToVideoSide() {
closeComment();
if ($commentWrap.parent().is($modal)) {
$videoSide.append($commentWrap);
}
clearMobileStyles();
}
function moveToModal() {
if (!$commentWrap.parent().is($modal)) {
$modal.append($commentWrap);
}
}
function handleResize() {
if (isMobileView()) {
moveToModal();
if ($modal.hasClass('is-comment-open')) {
applyCommentHeight(parseFloat($commentWrap[0].style.height) || getDefaultCommentHeight());
}
} else {
restoreToVideoSide();
}
}
function onResizerPointerMove(event) {
if (!dragState) return;
var pointerY = getPointerY(event);
if (!Number.isFinite(pointerY)) return;
var delta = dragState.startY - pointerY;
if (Math.abs(delta) > 2) {
dragState.moved = true;
}
applyCommentHeight(dragState.startHeight + delta);
if (event && typeof event.preventDefault === 'function') {
event.preventDefault();
}
}
function stopResizerDrag() {
dragState = null;
$(document).off('.insightCommentResize');
}
function startResizerDrag(event) {
if (event.type === 'mousedown' && event.which !== 1) return;
var pointerY = getPointerY(event);
if (!Number.isFinite(pointerY)) return;
if (isMobileView()) {
openComment();
}
dragState = {
startY: pointerY,
startHeight: $commentWrap.outerHeight() || getDefaultCommentHeight(),
moved: false
};
$(document)
.on('mousemove.insightCommentResize touchmove.insightCommentResize', onResizerPointerMove)
.on('mouseup.insightCommentResize touchend.insightCommentResize touchcancel.insightCommentResize', stopResizerDrag);
if (event && typeof event.preventDefault === 'function') {
event.preventDefault();
}
}
// 초기 상태 반영
if (isMobileView()) {
moveToModal();
} else {
restoreToVideoSide();
}
// matchMedia change 감지
if (typeof mediaQuery.addEventListener === 'function') {
mediaQuery.addEventListener('change', handleResize);
} else if (typeof mediaQuery.addListener === 'function') {
mediaQuery.addListener(handleResize);
}
// 버튼 클릭 토글
var onBtnClick = function (e) {
e.stopPropagation();
if (!isMobileView()) return;
if ($modal.hasClass('is-comment-open')) closeComment();
else openComment();
};
$btnComment.on('click', onBtnClick);
$commentWrap.on('mousedown touchstart', '.comment-resizer', startResizerDrag);
// 정리 함수 등록
commentToggleCleanup = function () {
try {
$btnComment.off('click', onBtnClick);
$commentWrap.off('mousedown touchstart', '.comment-resizer', startResizerDrag);
stopResizerDrag();
if (typeof mediaQuery.removeEventListener === 'function') {
mediaQuery.removeEventListener('change', handleResize);
} else if (typeof mediaQuery.removeListener === 'function') {
mediaQuery.removeListener(handleResize);
}
} catch (e) {}
restoreToVideoSide();
commentToggleCleanup = null;
};
}
// ── 모달 열기 ──
function openModal(info) {
currentContentId = info.contentId;
lastSaveTime = 0;
var baseWatchTm = Math.floor(info.watchTm || 0);
lastCurrentTime = baseWatchTm;
// YouTube IFrame API 플레이어 세팅
var videoId = extractVideoId(info.videoUrl);
if (videoId) {
// 배너 진입 시 0초 재생을 피하기 위해 서버 watch_tm 확정 후 1회만 초기화
$.ajax({
url: '/bbs/myclass/api/get_video_time.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ content_id: info.contentId }),
dataType: 'json',
success: function(res) {
var serverWatchTm = 0;
if (res && res.success && res.watch_tm > 0) {
serverWatchTm = parseInt(res.watch_tm, 10) || 0;
$('#video-list .video-item[data-content-id="' + info.contentId + '"]').attr('data-watch-tm', serverWatchTm);
}
var startTm = Math.max(baseWatchTm, serverWatchTm);
lastCurrentTime = startTm;
initYTPlayer(videoId, startTm);
},
error: function() {
lastCurrentTime = baseWatchTm;
initYTPlayer(videoId, baseWatchTm);
}
});
}
// 영상 정보
$modal.find('#modalTitle').text(info.title);
var descHtml = $('<div>').text(info.description || '').html().replace(/\n/g, '<br>');
$modal.find('#modalDesc').html(descHtml);
$modal.find('#modalCategoryName').text(info.categoryName || '인사이트');
$modal.find('#modalSubCategory').text(info.subCategory || '');
$modal.find('#modalBadge').text(info.categoryName || '인사이트');
// 추천영상 리스트
buildRecommendedList(info.contentId);
// 댓글 로드
loadComments(info.contentId);
resetCommentForm();
// 북마크 상태 동기화
syncBookmark(info.contentId, info.bookmark);
// 모달 표시
$modal.show();
$('body').css('overflow', 'hidden');
// 모바일 댓글 토글 세팅(1회 바인딩)
setupCommentToggle();
setTimeout(adjustRecommendedListHeight, 0);
}
// ── 모달 닫기 (진행상황 저장 후 닫기) ──
function closeModal() {
stopTracking();
if (ytPlayer && currentContentId && typeof ytPlayer.getCurrentTime === 'function') {
var currentTime = Math.floor(ytPlayer.getCurrentTime());
$('#video-list .video-item[data-content-id="' + currentContentId + '"]').attr('data-watch-tm', currentTime);
saveLearningProgress(Math.floor(lastSaveTime), 'N');
}
lastSaveTime = 0;
destroyYTPlayer();
$modal.hide();
$modal.removeClass('is-comment-open');
$('body').css('overflow', '');
currentContentId = null;
// 댓글 토글 리스너/DOM 정리
if (typeof commentToggleCleanup === 'function') {
commentToggleCleanup();
}
}
$modal.on('click', '.close', closeModal);
$modal.on('click', function (e) {
if ($(e.target).hasClass('modal')) closeModal();
});
$(document).on('keydown', function (e) {
if (e.key === 'Escape' && $modal.is(':visible')) closeModal();
});
// ── 북마크 동기화 ──
function syncBookmark(contentId, isBookmarked) {
var $chk = $modal.find('.bookmark input[type="checkbox"]');
if (!$chk.length) return;
if (isBookmarked !== undefined) {
$chk.prop('checked', !!isBookmarked);
} else {
// 서버에서 북마크 상태 조회
$.ajax({
url: '/bbs/myclass/api/get_video_time.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ content_id: contentId }),
dataType: 'json',
success: function(res) {
if (res.success) {
$chk.prop('checked', !!res.is_bookmarked);
}
}
});
}
}
// ── 북마크 토글 이벤트 ──
$modal.on('change', '.bookmark input[type="checkbox"]', function () {
var $chk = $(this);
var isActive = $chk.is(':checked') ? '1' : '0';
if (!currentContentId) return;
$chk.prop('disabled', true);
$.ajax({
url: '/bbs/api/save_wishlist.php',
type: 'POST',
data: { content_id: String(currentContentId), is_active: isActive },
dataType: 'json',
success: function (res) {
if (!res || res.success !== true) {
$chk.prop('checked', !$chk.is(':checked'));
alert(res?.message || '북마크 저장에 실패했습니다.');
} else {
// 목록 카드의 북마크 상태도 동기화
var $card = $('#video-list .video-item[data-content-id="' + currentContentId + '"] .bookmark input');
$card.prop('checked', isActive === '1');
}
},
error: function () {
$chk.prop('checked', !$chk.is(':checked'));
alert('북마크 저장 중 오류가 발생했습니다.');
},
complete: function () {
$chk.prop('disabled', false);
}
});
});
// ── 추천영상 리스트 구성 (현재 페이지 영상 목록 활용) ──
function buildRecommendedList(excludeContentId) {
var $list = $modal.find('#recommendedList');
$list.empty();
var count = 0;
$('#video-list .video-item').each(function () {
var $item = $(this);
var cid = $item.data('content-id') || $item.attr('data-content-id') || '';
if (!cid || String(cid) === String(excludeContentId)) return;
if (count >= 10) return false;
var videoUrl = $item.data('content-url') || $item.attr('data-content-url') || '';
var title = $item.data('title') || $item.find('.item-title').text().trim() || '';
var catName = $item.data('category-name') || $item.attr('data-category-name') || $item.find('.item-category').text().trim() || '인사이트';
var thumbSrc = $item.find('.item-thumb img, .thumb img').attr('src') || '/img/video/img_thumb_01.png';
var desc = String($item.data('description') || '');
var subCat = $item.data('sub-category') || $item.attr('data-sub-category') || '';
var wTm = parseInt($item.data('watch-tm') || $item.attr('data-watch-tm') || '0', 10);
var cTm = parseInt($item.data('content-tm') || $item.attr('data-content-tm') || '0', 10);
var li =
'<li>' +
'<a href="#" class="list" ' +
'data-content-id="' + cid + '" ' +
'data-content-url="' + videoUrl.replace(/"/g, '&quot;') + '" ' +
'data-title="' + title.replace(/"/g, '&quot;') + '" ' +
'data-description="' + desc.replace(/"/g, '&quot;') + '" ' +
'data-category-name="' + catName.replace(/"/g, '&quot;') + '" ' +
'data-sub-category="' + subCat.replace(/"/g, '&quot;') + '" ' +
'data-watch-tm="' + wTm + '" ' +
'data-content-tm="' + cTm + '">' +
'<div class="thumb"><img src="' + thumbSrc + '" alt="" /></div>' +
'<div class="txt-box">' +
'<div class="category leader">' + catName + '</div>' +
'<div class="title">' + title + '</div>' +
'</div>' +
'</a>' +
'</li>';
$list.append(li);
count++;
});
if (count === 0) {
$list.html('<li class="empty">추천 영상이 없습니다.</li>');
}
setTimeout(adjustRecommendedListHeight, 0);
}
// ── 추천영상 클릭 → 영상 전환 ──
$modal.on('click', '#recommendedList .list', function (e) {
e.preventDefault();
var $a = $(this);
stopTracking();
if (ytPlayer && currentContentId && typeof ytPlayer.getCurrentTime === 'function') {
saveLearningProgress(Math.floor(lastSaveTime), 'N');
}
lastSaveTime = 0;
openModal({
contentId: $a.data('content-id'),
videoUrl: $a.data('content-url') || '',
title: $a.data('title') || $a.find('.title').text().trim(),
description: $a.data('description') || '',
categoryName: $a.data('category-name') || $a.attr('data-category-name') || '인사이트',
subCategory: $a.data('sub-category') || $a.attr('data-sub-category') || '',
watchTm: parseInt($a.data('watch-tm') || '0', 10),
contentTm: parseInt($a.data('content-tm') || '0', 10)
});
});
$(window).on('resize', function () {
if ($modal.is(':visible')) {
adjustRecommendedListHeight();
}
});
// ══════════════════════════════════════════════
// 댓글 CRUD
// ══════════════════════════════════════════════
// ── 댓글 목록 로드 ──
function loadComments(contentId) {
var $commentList = $modal.find('.comment-list');
if (!contentId) return;
$.getJSON(API_BASE + '/get_comments.php', { content_id: contentId })
.done(function (data) {
if (data.success && Array.isArray(data.data)) {
renderComments(data.data);
} else {
$commentList.html('<li class="empty">등록된 댓글이 없습니다.</li>');
}
})
.fail(function () {
$commentList.html('<li class="empty">댓글을 불러올 수 없습니다.</li>');
});
}
// ── 댓글 렌더링 (온보딩 VideoModalBase 동일 구조) ──
function renderComments(comments) {
var $commentList = $modal.find('.comment-list');
if (!comments.length) {
$commentList.html('<li class="empty-comment" style="padding:20px; text-align:center; color:rgba(255,255,255,0.4); font-size:13px;">작성된 댓글이 없습니다.</li>');
return;
}
var html = comments.map(function (c) {
var safeComment = (c.comment || '')
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
var safeName = (c.member_name || '익명')
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
var actions = c.is_author
? '<div class="actions">' +
'<button type="button" class="btn-edit-comment" data-id="' + c.id + '">수정</button>' +
'<button type="button" class="btn-del-comment" data-id="' + c.id + '">삭제</button>' +
'</div>'
: '';
return '<li data-id="' + c.id + '">' +
'<div class="comment-info">' +
'<div class="photo"><img src="' + (c.profile_image || '/img/ico/ico_user.svg') + '" onerror="this.src=\'/img/ico/ico_user.svg\'" /></div>' +
'<div class="user-comment">' +
'<span class="user-name">' + safeName + '</span>' +
'<textarea class="user-text" disabled>' + safeComment + '</textarea>' +
'</div>' +
actions +
'</div>' +
'</li>';
}).join('');
$commentList.html(html);
adjustUserTextHeights();
}
// ── 댓글 textarea 높이 자동 조정 ──
function adjustUserTextHeights() {
$modal.find('.user-text').each(function () {
this.style.setProperty('overflow-y', 'hidden', 'important');
this.style.setProperty('overflow-x', 'hidden', 'important');
this.style.setProperty('max-height', 'none', 'important');
this.style.setProperty('height', '0px', 'important');
var h = this.scrollHeight;
this.style.setProperty('height', (h > 0 ? h : 40) + 'px', 'important');
});
}
// ── 댓글 인라인 수정 (온보딩 동일) ──
$modal.on('click', '.btn-edit-comment', function (e) {
e.preventDefault();
var $li = $(this).closest('li');
var $textarea = $li.find('.user-text');
var $btn = $(this);
if ($btn.text() === '수정') {
$textarea.prop('disabled', false).focus();
$btn.text('저장').css('color', '#00ffcc');
$li.addClass('editing');
} else {
var newComment = $textarea.val().trim();
if (!newComment) return;
if (newComment.length > COMMENT_MAX_LENGTH) {
alert('댓글은 200자까지 입력할 수 있습니다.');
$textarea.val($textarea.val().slice(0, COMMENT_MAX_LENGTH));
return;
}
var commentId = $btn.data('id');
$.ajax({
url: API_BASE + '/save_comment.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ content_id: currentContentId, comment: newComment, id: commentId }),
success: function (data) {
if (data.success) {
$btn.text('수정').css('color', '');
$textarea.prop('disabled', true);
$li.removeClass('editing');
loadComments(currentContentId);
} else {
alert(data.message || '저장에 실패했습니다.');
}
},
error: function () { alert('저장 중 오류가 발생했습니다.'); }
});
}
});
// ── 댓글 삭제 (온보딩 동일) ──
$modal.on('click', '.btn-del-comment', function (e) {
e.preventDefault();
if (!confirm('정말 삭제하시겠습니까?')) return;
var commentId = $(this).data('id');
$.ajax({
url: API_BASE + '/delete_comment.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ id: commentId }),
success: function (data) {
if (data.success) {
loadComments(currentContentId);
} else {
alert(data.message || '삭제에 실패했습니다.');
}
},
error: function () { alert('삭제 중 오류가 발생했습니다.'); }
});
});
// ── 새 댓글 작성 ──
$modal.on('click', '.comment-box .btn-save', function () {
var $textarea = $modal.find('.comment-box textarea');
var comment = $textarea.val().trim();
if (!comment || !currentContentId) return;
if (comment.length > COMMENT_MAX_LENGTH) {
$textarea.val($textarea.val().slice(0, COMMENT_MAX_LENGTH));
alert('댓글은 200자까지 입력할 수 있습니다.');
return;
}
var payload = { content_id: currentContentId, comment: comment };
$.ajax({
url: API_BASE + '/save_comment.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(payload),
success: function (data) {
if (data.success) {
resetCommentForm();
loadComments(currentContentId);
} else {
alert(data.message || '저장에 실패했습니다.');
}
},
error: function () { alert('저장 중 오류가 발생했습니다.'); }
});
});
// ── 댓글 취소 ──
$modal.on('click', '.comment-box .btn-cancel', function () {
resetCommentForm();
});
var COMMENT_MAX_LENGTH = 200;
var _lastLengthAlertAt = 0;
// ── textarea 입력 → 버튼 활성화 + 200자 제한 ──
$modal.on('input', '.comment-box textarea', function () {
var $ta = $(this);
if ($ta.val().length > COMMENT_MAX_LENGTH) {
$ta.val($ta.val().slice(0, COMMENT_MAX_LENGTH));
var now = Date.now();
if (now - _lastLengthAlertAt > 800) {
_lastLengthAlertAt = now;
alert('댓글은 200자까지 입력할 수 있습니다.');
}
}
var hasText = $ta.val().trim().length > 0;
$modal.find('.btn-cancel').prop('disabled', !hasText);
$modal.find('.btn-save').prop('disabled', !hasText);
});
function resetCommentForm() {
var $ta = $modal.find('.comment-box textarea');
$ta.val('');
$ta.attr('maxlength', COMMENT_MAX_LENGTH);
$modal.find('.btn-cancel').prop('disabled', true);
$modal.find('.btn-save').prop('disabled', true).text('등록');
}
});
</script>
</body>
</html>