Files
edu/skin/leadership_222.php
T

965 lines
35 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.
<!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 = 'L'; //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="./leadership.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 foreach ($array_banner_img as $i => $bn): ?>
<div class="swiper-slide">
<div class="banner-img">
<picture>
<source media="(min-width: 769px)" srcset="<?= $bn['normal'] ?>">
<source media="(max-width: 768px)" srcset="<?= $bn['mobile'] ?>">
<img src="<?= $bn['normal'] ?>" alt="<?= $bn['title'] ?>">
</picture>
</div>
</div>
<?php endforeach; ?>
</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">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 currentText = $('.current').find('.tab-text').text();
if(currentText==""){$('.current').text("리더십 입문");}
*/
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;
//let category = $('.leadership-tab.is-active').data('cate') || 'CA200L01';
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 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
},
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);
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);
}
}
},
error: function () {
if (page > 1) page--;
},
complete: function () {
if (currentRequestSeq === requestSeq) {
setLoading(false);
}
}
});
}
});
</script>
<!-- ============================================ -->
<!-- 영상 모달 댓글 스타일 (온보딩 동일) -->
<!-- ============================================ -->
<style>
/* #leadershipVideoModal .comment-list { list-style: none; margin: 0; padding: 0; }
#leadershipVideoModal .comment-list li { padding: 0; border-bottom: none; }
#leadershipVideoModal .comment-list li.empty-comment { padding: 20px; text-align: center; color: rgba(255,255,255,0.4); font-size: 13px; }
#leadershipVideoModal .comment-list .comment-info { display: flex; align-items: flex-start; }
#leadershipVideoModal .comment-list .comment-info .photo { flex-shrink: 0; margin-right: 6px; display: flex; align-items: center; }
#leadershipVideoModal .comment-list .comment-info .photo img { width: 20px; height: 20px; border-radius: 50%; }
#leadershipVideoModal .comment-list .comment-info .user-comment { flex: 1; min-width: 0; display: flex; align-items: center; }
#leadershipVideoModal .comment-list .comment-info .user-comment .user-name { font-size: 13px; color: #ccc; white-space: nowrap; flex-shrink: 0; margin-right: 8px; }
#leadershipVideoModal .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; }
#leadershipVideoModal .comment-list .comment-info .user-comment .user-text:disabled { opacity: 1; cursor: default; padding-top: 10px;}
#leadershipVideoModal .comment-list .comment-info .user-comment .user-text:not(:disabled) { border: 1px solid #555; border-radius: 4px; padding: 4px 8px; background: #2a2a2a; }
#leadershipVideoModal .comment-list .comment-info .actions { display: flex; gap: 4px; align-items: center; flex-shrink: 0; margin-left: 8px; align-self: center; }
#leadershipVideoModal .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; }
#leadershipVideoModal .comment-list .comment-info .actions button:hover { background: #555; color: #fff; }
#leadershipVideoModal .comment-list li.editing .user-text { border: 1px solid #00ffcc !important; } */
</style>
<!-- ============================================ -->
<!-- 영상 모달 (video.php 구조 참고) -->
<!-- ============================================ -->
<div class="modal video" id="leadershipVideoModal" 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>
<label class="bookmark" for="leadership-bookmark-chk">
<input id="leadership-bookmark-chk" type="checkbox" value="" />
<span>북마크</span>
</label>
</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-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 = $('#leadershipVideoModal');
var currentContentId = null;
var editingCommentId = 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('[Leadership] Player Error:', event.data);
}
}
});
};
if (typeof YT !== 'undefined' && YT.Player && typeof YT.Player === 'function') {
createPlayer();
} else {
// YT API 아직 로드 안 됨 → 대기
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;
// YT.Player가 div를 iframe으로 교체하므로 다시 div를 복원
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 >= 5) {
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('[Leadership] Save Learning Failed:', result.message);
}
}).catch(function (e) {
console.error('[Leadership] Save Learning Error:', e);
});
}
// ── 영상 카드 클릭 → 모달 열기 ──
$(document).on('click', '#video-list .video-item .card-link', function (e) {
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.find('.item-category').text().trim() || '리더십';
var subCat = $item.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
});
});
// ── 모달 열기 ──
function openModal(info) {
currentContentId = info.contentId;
editingCommentId = null;
lastSaveTime = 0;
lastCurrentTime = Math.floor(info.watchTm || 0);
// YouTube IFrame API 플레이어 세팅
var videoId = extractVideoId(info.videoUrl);
if (videoId) {
initYTPlayer(videoId, info.watchTm || 0);
}
// 서버에서 최신 watch_tm 조회 후 재생 위치 보정
$.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) {
if (res.success && res.watch_tm > 0) {
var serverWatchTm = parseInt(res.watch_tm, 10);
if (ytPlayer && typeof ytPlayer.seekTo === 'function' && serverWatchTm > (info.watchTm || 0)) {
ytPlayer.seekTo(serverWatchTm, true);
lastCurrentTime = serverWatchTm;
}
// 카드 data-watch-tm 동기화
$('#video-list .video-item[data-content-id="' + info.contentId + '"]').attr('data-watch-tm', serverWatchTm);
}
}
});
// 영상 정보
$modal.find('#modalTitle').text(info.title);
// 설명: 줄바꿈을 <br>로 변환하여 표시
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');
}
// ── 모달 닫기 (진행상황 저장 후 닫기) ──
function closeModal() {
// 마지막 시청 진행상황 저장
stopTracking();
if (ytPlayer && currentContentId && typeof ytPlayer.getCurrentTime === 'function') {
var currentTime = Math.floor(ytPlayer.getCurrentTime());
// 카드의 data-watch-tm 업데이트 (재오픈 시 적용)
$('#video-list .video-item[data-content-id="' + currentContentId + '"]').attr('data-watch-tm', currentTime);
saveLearningProgress(Math.floor(lastSaveTime), 'N');
}
lastSaveTime = 0;
destroyYTPlayer();
$modal.hide();
$('body').css('overflow', '');
currentContentId = null;
editingCommentId = null;
}
$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);
}
});
});
// ── 추천영상 리스트 구성 (키워드 기반 API) ──
function buildRecommendedList(contentId) {
var $list = $modal.find('#recommendedList');
$list.html('<li style="padding:20px; text-align:center; color:rgba(255,255,255,0.4); font-size:13px;">추천 영상 로딩 중...</li>');
if (!contentId) {
$list.html('<li style="padding:20px; text-align:center; color:rgba(255,255,255,0.4); font-size:13px;">관련 추천 영상이 없습니다.</li>');
return;
}
$.getJSON(API_BASE + '/get_recommend_videos.php', { content_id: contentId })
.done(function (data) {
if (!data.success || !data.videos || data.videos.length === 0) {
$list.html('<li style="padding:20px; text-align:center; color:rgba(255,255,255,0.4); font-size:13px;">관련 추천 영상이 없습니다.</li>');
// 키워드 배지 숨기기
$modal.find('#modalBadge').hide();
return;
}
// 키워드 배지 업데이트
if (data.keywords && data.keywords.length > 0) {
$modal.find('#modalBadge').text(data.keywords.join(', ')).show();
} else {
$modal.find('#modalBadge').hide();
}
$list.empty();
var catClassMap = {
'CA10001': 'myclass', 'CA10002': 'onboarding', 'CA10003': 'legal',
'CA10004': 'leader', 'CA10005': 'insight', 'CA10006': 'biztrend'
};
data.videos.forEach(function (v) {
var safeTitle = $('<div>').text(v.title || '').html();
var safeCat = $('<div>').text(v.category_name || '').html();
var thumb = v.thumbnail || '/img/video/img_thumb_01.png';
var catClass = catClassMap[v.category_code] || '';
var li =
'<li>' +
'<a href="#" class="list" ' +
'data-content-id="' + (v.content_id || '') + '" ' +
'data-content-url="' + (v.content_url || '').replace(/"/g, '&quot;') + '" ' +
'data-title="' + (v.title || '').replace(/"/g, '&quot;') + '" ' +
'data-description="' + (v.description || '').replace(/"/g, '&quot;') + '" ' +
'data-category-name="' + (v.category_name || '').replace(/"/g, '&quot;') + '">' +
'<div class="thumb"><img src="' + thumb + '" alt="" /></div>' +
'<div class="txt-box">' +
'<div class="category ' + catClass + '">' + safeCat + '</div>' +
'<div class="title">' + safeTitle + '</div>' +
'</div>' +
'</a>' +
'</li>';
$list.append(li);
});
})
.fail(function () {
$list.html('<li style="padding:20px; text-align:center; color:rgba(255,255,255,0.4); font-size:13px;">관련 추천 영상이 없습니다.</li>');
});
}
// ── 추천영상 클릭 → 영상 전환 ──
$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') || '리더십'
});
});
// ══════════════════════════════════════════════
// 댓글 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="/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.height = 'auto';
this.style.height = this.scrollHeight + 'px';
this.style.overflow = 'hidden';
});
}
// ── 댓글 인라인 수정 (온보딩 동일) ──
$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;
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;
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();
});
// ── textarea 입력 → 버튼 활성화 ──
$modal.on('input', '.comment-box textarea', function () {
var hasText = $(this).val().trim().length > 0;
$modal.find('.btn-cancel').prop('disabled', !hasText);
$modal.find('.btn-save').prop('disabled', !hasText);
});
function resetCommentForm() {
$modal.find('.comment-box textarea').val('');
$modal.find('.btn-cancel').prop('disabled', true);
$modal.find('.btn-save').prop('disabled', true).text('등록');
editingCommentId = null;
}
});
</script>
</body>
</html>