Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../bbs/auth.php';
|
||||
edu_require_login();
|
||||
|
||||
require_once __DIR__ . '/../bbs/index.php';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<?php include(__DIR__ . "/_include/_head.php") ?>
|
||||
<link rel="stylesheet" type="text/css" href="/edu/css/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap main">
|
||||
<?php include(__DIR__ . "/_include/_header.php") ?>
|
||||
<div class="container">
|
||||
<div class="bg-circle">
|
||||
<ul>
|
||||
<li><div></div></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="learning-area">
|
||||
<svg id="gauge" viewBox="0 0 794 460" preserveAspectRatio="xMidYMid meet"></svg>
|
||||
</div>
|
||||
<div class="main-contents">
|
||||
<div class="text-box">
|
||||
<span><em><?= htmlspecialchars($userName) ?></em> <?= htmlspecialchars($userRank) ?>님</span>
|
||||
|
||||
<div class="keyword-area">
|
||||
<button id="keywordSettingsBtn">나의 키워드 <i class="ico-setting"></i></button>
|
||||
|
||||
<!-- [DEBUG] myKeywords:<?= count($myKeywords) ?>, adminKeywords:<?= count($adminKeywords) ?>, allKeywords:<?= count($allKeywords) ?>, error:<?= htmlspecialchars($mainDataError ?? '') ?> -->
|
||||
<div class="keyword-list" id="myKeyword" style="display:flex;flex-wrap:wrap;gap:4px;">
|
||||
<?php foreach ($myKeywords as $i => $kw): ?>
|
||||
<label class="kw-allow" for="chk_my_<?= $i ?>" style="display:inline-flex;align-items:center;gap:4px;">
|
||||
<input type="checkbox" id="chk_my_<?= $i ?>" checked />
|
||||
#<?= htmlspecialchars($kw['keyword_name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="keyword-list" style="display:flex;flex-wrap:wrap;gap:4px;">
|
||||
<?php foreach ($adminKeywords as $i => $kw): ?>
|
||||
<label class="kw-deny" for="chk_admin_<?= $i ?>" style="display:inline-flex;align-items:center;gap:4px;">
|
||||
<input type="checkbox" id="chk_admin_<?= $i ?>" checked />
|
||||
#<?= htmlspecialchars($kw['keyword_name']) ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<p><em>취향저격 영상 추천</em>드려요.</p>
|
||||
</div>
|
||||
|
||||
<div class="video-wrap">
|
||||
<div class="video-cards-container" id="videoCardsContainer"></div>
|
||||
<!-- <button class="btn-prev" id="prevBtn">‹</button>
|
||||
<button class="btn-next" id="nextBtn">›</button> -->
|
||||
<!-- <div class="pagination" id="pagination"><span class="current">1</span> / 1</div> -->
|
||||
</div>
|
||||
|
||||
<?php include(__DIR__ . "/_modal/keyword.php") ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/edu/js/main/VideoCardRenderer.js" defer></script>
|
||||
<script src="/edu/js/main/VideoSlider.js" defer></script>
|
||||
<script src="/edu/js/main/Videomodalmanager.js" defer></script>
|
||||
<script src="/edu/js/main/Gaugechart.js" defer></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// DEBUG: 초기 상태 확인
|
||||
console.log('[INDEX DEBUG]', {
|
||||
videosJson: <?= $videosJson ?>,
|
||||
myKwJson: <?= $myKwJson ?>,
|
||||
totalMin: <?= $totalMin ?>,
|
||||
avgWatchMin: <?= $avgWatchMin ?>,
|
||||
userName: <?= json_encode($userName) ?>,
|
||||
userRank: <?= json_encode($userRank) ?>,
|
||||
allKeywordsCount: <?= count($allKeywords) ?>,
|
||||
myKeywordsCount: <?= count($myKeywords) ?>,
|
||||
});
|
||||
|
||||
// 상세 진단 정보 조회
|
||||
fetch('/edu/bbs/api/diagnosis.php')
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
console.log('[DIAGNOSIS API]', d);
|
||||
if (d.saved_keywords_count === 0) {
|
||||
console.warn('⚠️ 저장된 키워드가 없습니다. 모달에서 키워드를 선택하고 저장해주세요.');
|
||||
}
|
||||
})
|
||||
.catch(e => console.error('[DIAGNOSIS] fetch failed', e));
|
||||
|
||||
const videos = <?= $videosJson ?>;
|
||||
let currentVideos = Array.isArray(videos) ? videos : [];
|
||||
|
||||
let allowKeywords = [];
|
||||
document.querySelectorAll('#myKeyword .kw-allow input[type="checkbox"]').forEach((cb) => {
|
||||
const label = cb.closest('label');
|
||||
if (!label) return;
|
||||
const kw = label.textContent.trim().replace(/^#/, '').trim();
|
||||
if (kw) allowKeywords.push(kw);
|
||||
});
|
||||
if (allowKeywords.length === 0) {
|
||||
allowKeywords = <?= $myKwJson ?>;
|
||||
}
|
||||
console.log('[KEYWORDS INIT] allowKeywords (from HTML or myKwJson):', allowKeywords);
|
||||
|
||||
const ACTIVE_KEY = 'edu_kw_active';
|
||||
const allAdminKwList = Array.from(document.querySelectorAll('.kw-deny')).map((lbl) => lbl.textContent.trim().replace(/^#/, '').trim()).filter(Boolean);
|
||||
console.log('[KEYWORDS INIT] allAdminKwList (from .kw-deny):', allAdminKwList);
|
||||
|
||||
let activeMyKeywords = [...allowKeywords];
|
||||
let activeAdminKeywords = [...allAdminKwList];
|
||||
console.log('[KEYWORDS INIT] activeMyKeywords:', activeMyKeywords, ', activeAdminKeywords:', activeAdminKeywords);
|
||||
// [DEBUG] 관리자 추천 키워드 HTML 렌더링 확인
|
||||
console.log('[ADMIN KEYWORDS FINAL]', {
|
||||
count: document.querySelectorAll('.kw-deny').length,
|
||||
keywords: Array.from(document.querySelectorAll('.kw-deny')).map(lbl => lbl.textContent.trim()),
|
||||
});
|
||||
const _stored = JSON.parse(localStorage.getItem(ACTIVE_KEY) || 'null');
|
||||
if (_stored) {
|
||||
if (Array.isArray(_stored.myActive)) {
|
||||
const filtered = allowKeywords.filter((k) => _stored.myActive.includes(k));
|
||||
activeMyKeywords = filtered;
|
||||
}
|
||||
if (Array.isArray(_stored.adminActive)) {
|
||||
const filtered = allAdminKwList.filter((k) => _stored.adminActive.includes(k));
|
||||
if (filtered.length > 0) activeAdminKeywords = filtered;
|
||||
}
|
||||
}
|
||||
|
||||
const keywordModal = document.getElementById('keywordModal');
|
||||
const keywordSettingsBtn = document.getElementById('keywordSettingsBtn');
|
||||
const keywordModalCloseBtn = keywordModal?.querySelector('.btn-close');
|
||||
const keywordTagList = keywordModal?.querySelector('.keyword-tag');
|
||||
|
||||
function getKeywordFromLabelText(text) {
|
||||
return String(text || '').trim().replace(/^#/, '').trim();
|
||||
}
|
||||
|
||||
function getModalKeywordLabels() {
|
||||
if (!keywordTagList) return [];
|
||||
return Array.from(keywordTagList.querySelectorAll('label'))
|
||||
.map((label) => getKeywordFromLabelText(label.textContent))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function ensureModalKeywords() {
|
||||
if (!keywordTagList) return;
|
||||
|
||||
const existing = getModalKeywordLabels();
|
||||
if (existing.length > 0) return;
|
||||
|
||||
const fallbackKeywords = Array.from(new Set([
|
||||
...allowKeywords,
|
||||
...allAdminKwList,
|
||||
...activeMyKeywords,
|
||||
])).filter(Boolean);
|
||||
|
||||
keywordTagList.innerHTML = fallbackKeywords.map((kw, idx) => {
|
||||
const safeId = `kwtag_fallback_${idx + 1}`;
|
||||
return `\n<li class="kw-box">\n <label for="${safeId}">\n <input type="checkbox" id="${safeId}" />\n #${kw}\n </label>\n</li>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function syncModalSelection() {
|
||||
if (!keywordTagList) return;
|
||||
const selected = new Set(activeMyKeywords);
|
||||
keywordTagList.querySelectorAll('input[type="checkbox"]').forEach((cb) => {
|
||||
const label = cb.closest('label');
|
||||
const kw = getKeywordFromLabelText(label?.textContent || '');
|
||||
cb.checked = selected.has(kw);
|
||||
});
|
||||
}
|
||||
|
||||
function renderMyKeywords(selectedKeywords) {
|
||||
const myKeywordEl = document.getElementById('myKeyword');
|
||||
if (!myKeywordEl) return;
|
||||
|
||||
myKeywordEl.innerHTML = selectedKeywords.map((kw, idx) => `\n<label class="kw-allow" for="chk_my_${idx}">\n <input type="checkbox" id="chk_my_${idx}" checked />\n #${kw}\n</label>`).join('');
|
||||
}
|
||||
|
||||
function openKeywordModal() {
|
||||
if (!keywordModal) return;
|
||||
ensureModalKeywords();
|
||||
syncModalSelection();
|
||||
keywordModal.classList.add('is-open');
|
||||
keywordModal.style.display = 'block';
|
||||
keywordModal.setAttribute('aria-hidden', 'false');
|
||||
document.body.classList.add('modal-open');
|
||||
// 접근성: 모달 열릴 때 닫기 버튼으로 포커스 이동
|
||||
keywordModalCloseBtn?.focus();
|
||||
}
|
||||
|
||||
function closeKeywordModal(save = false) {
|
||||
if (!keywordModal) return;
|
||||
|
||||
if (save && keywordTagList) {
|
||||
const selectedMy = Array.from(keywordTagList.querySelectorAll('input[type="checkbox"]'))
|
||||
.filter((cb) => cb.checked)
|
||||
.map((cb) => getKeywordFromLabelText(cb.closest('label')?.textContent || ''))
|
||||
.filter(Boolean)
|
||||
.slice(0, 3); // 최대 3개
|
||||
|
||||
// 0개 포함 항상 저장 (사용자가 모두 해제한 경우도 반영)
|
||||
activeMyKeywords = Array.from(new Set(selectedMy));
|
||||
renderMyKeywords(activeMyKeywords);
|
||||
saveActiveState();
|
||||
// DB 저장 (백그라운드)
|
||||
fetch('/edu/bbs/api/user_keywords.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ keywords: activeMyKeywords }),
|
||||
}).then(async (res) => {
|
||||
const d = await res.json().catch(() => ({}));
|
||||
if (!d.success) console.warn('[user-keywords save] 실패', d);
|
||||
}).catch((e) => console.warn('[user-keywords save]', e));
|
||||
fetchAndRefreshVideos();
|
||||
}
|
||||
|
||||
// 접근성: aria-hidden 설정 전에 반드시 포커스를 모달 밖으로 이동
|
||||
keywordSettingsBtn?.focus();
|
||||
keywordModal.classList.remove('is-open');
|
||||
keywordModal.style.display = 'none';
|
||||
keywordModal.setAttribute('aria-hidden', 'true');
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
|
||||
keywordSettingsBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
openKeywordModal();
|
||||
});
|
||||
|
||||
keywordModalCloseBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
closeKeywordModal(true);
|
||||
});
|
||||
|
||||
keywordModal?.addEventListener('click', (e) => {
|
||||
if (e.target === keywordModal) {
|
||||
closeKeywordModal(false);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && keywordModal?.classList.contains('is-open')) {
|
||||
closeKeywordModal(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 키워드 모달 체크박스 3개 제한
|
||||
const MAX_KEYWORDS = 3;
|
||||
keywordTagList?.addEventListener('change', (e) => {
|
||||
const cb = e.target.closest('input[type="checkbox"]');
|
||||
if (!cb || !cb.checked) return;
|
||||
const checked = keywordTagList.querySelectorAll('input[type="checkbox"]:checked');
|
||||
if (checked.length > MAX_KEYWORDS) {
|
||||
cb.checked = false;
|
||||
alert('키워드는 최대 ' + MAX_KEYWORDS + '개까지 선택 가능합니다.');
|
||||
}
|
||||
});
|
||||
|
||||
let renderer = new VideoCardRenderer({ animationDelay: 50 });
|
||||
let slider = new VideoSlider({
|
||||
videos: currentVideos,
|
||||
videosPerPage: 6,
|
||||
onPageChange: (pageVideos) => renderer.renderCards(pageVideos),
|
||||
});
|
||||
let modalManager = new VideoModalManager({ videos: currentVideos });
|
||||
|
||||
slider.init();
|
||||
renderer.renderCards(slider.getCurrentPageVideos());
|
||||
modalManager.init();
|
||||
|
||||
function extractYouTubeId(url) {
|
||||
const raw = String(url || '').trim();
|
||||
const matched = raw.match(/(?:v=|youtu\.be\/|youtube\.com\/embed\/)([A-Za-z0-9_-]{11})/);
|
||||
if (matched && matched[1]) return matched[1];
|
||||
if (/^[A-Za-z0-9_-]{11}$/.test(raw)) return raw;
|
||||
return '';
|
||||
}
|
||||
|
||||
function bindCardOpenFallback() {
|
||||
const container = document.getElementById('videoCardsContainer');
|
||||
if (!container || container.dataset.modalFallbackBound === '1') return;
|
||||
|
||||
container.dataset.modalFallbackBound = '1';
|
||||
container.addEventListener('click', (e) => {
|
||||
if (e.defaultPrevented) return;
|
||||
|
||||
const card = e.target.closest('.card[data-video-id]');
|
||||
if (!card || !container.contains(card)) return;
|
||||
|
||||
e.preventDefault();
|
||||
const videoId = card.getAttribute('data-video-id');
|
||||
if (!videoId) return;
|
||||
|
||||
try {
|
||||
if (modalManager && typeof modalManager.openVideo === 'function') {
|
||||
modalManager.openVideo(videoId);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[video-open fallback] modal open failed', err);
|
||||
}
|
||||
|
||||
const selected = (Array.isArray(currentVideos) ? currentVideos : []).find((item) => String(item.id) === String(videoId));
|
||||
const ytId = extractYouTubeId(selected && selected.url ? selected.url : '');
|
||||
if (ytId) {
|
||||
window.open(`https://www.youtube.com/watch?v=${ytId}`, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function saveWishlist(videoId, isActive) {
|
||||
const params = new URLSearchParams({
|
||||
content_id: String(videoId || ''),
|
||||
is_active: isActive ? '1' : '0',
|
||||
});
|
||||
|
||||
const res = await fetch('/edu/bbs/api/save_wishlist.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: params.toString(),
|
||||
});
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function setBookmarkState(videoId, isActive) {
|
||||
currentVideos = (Array.isArray(currentVideos) ? currentVideos : []).map((v) => {
|
||||
if (String(v.id) === String(videoId)) {
|
||||
return { ...v, bookmark: !!isActive };
|
||||
}
|
||||
return v;
|
||||
});
|
||||
|
||||
document.querySelectorAll(`#videoCardsContainer .card[data-video-id="${String(videoId)}"]`).forEach((card) => {
|
||||
const cb = card.querySelector('.bookmark input[type="checkbox"]');
|
||||
if (cb) cb.checked = !!isActive;
|
||||
});
|
||||
}
|
||||
|
||||
function bindWishlistEvents() {
|
||||
const container = document.getElementById('videoCardsContainer');
|
||||
if (!container || container.dataset.wishlistBound === '1') return;
|
||||
container.dataset.wishlistBound = '1';
|
||||
|
||||
container.addEventListener('click', (e) => {
|
||||
// 하트 클릭 시 카드 오픈만 막고, 체크 토글은 허용해야 change 이벤트가 발생함
|
||||
if (e.target.closest('.bookmark')) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
container.addEventListener('change', async (e) => {
|
||||
const checkbox = e.target.closest('.bookmark input[type="checkbox"]');
|
||||
if (!checkbox) return;
|
||||
|
||||
const card = checkbox.closest('.card[data-video-id]');
|
||||
const videoId = card?.getAttribute('data-video-id');
|
||||
if (!videoId) return;
|
||||
|
||||
const nextState = !!checkbox.checked;
|
||||
checkbox.disabled = true;
|
||||
|
||||
try {
|
||||
const result = await saveWishlist(videoId, nextState);
|
||||
if (!result || !result.success) {
|
||||
checkbox.checked = !nextState;
|
||||
console.warn('[wishlist] save failed', {
|
||||
videoId,
|
||||
nextState,
|
||||
result,
|
||||
});
|
||||
return;
|
||||
}
|
||||
setBookmarkState(videoId, nextState);
|
||||
} catch (err) {
|
||||
checkbox.checked = !nextState;
|
||||
console.warn('[wishlist]', err?.message || err);
|
||||
} finally {
|
||||
checkbox.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bindCardOpenFallback();
|
||||
bindWishlistEvents();
|
||||
|
||||
let gaugeMaxValue = <?= (int)$avgWatchMin ?>;
|
||||
let gaugeTotalMin = <?= (int)$totalMin ?>;//추가
|
||||
console.warn('[gaugeMaxValue]', gaugeMaxValue);
|
||||
console.warn('[gaugeTotalMin]', gaugeTotalMin);
|
||||
|
||||
// const initialGaugeMinutes = Math.min(gaugeMaxValue, <?= (int)$totalMin ?>);//기존
|
||||
const initialGaugeMinutes = Math.max(gaugeMaxValue, <?= (int)$totalMin ?>);//변경
|
||||
let effectiveMaxValue = Math.max(gaugeMaxValue, gaugeTotalMin);//추가
|
||||
|
||||
console.warn('[initialGaugeMinutes]', initialGaugeMinutes);//추가
|
||||
|
||||
//const gauge = new GaugeChart({ size: 832, strokeWidth: 31, maxValue: gaugeMaxValue, padding: 20, outerTextOffset: 6, innerTextOffset: 35, dotRadius: 7 });//기존
|
||||
const gauge = new GaugeChart({ size: 832, strokeWidth: 31, maxValue: effectiveMaxValue, padding: 20, outerTextOffset: 6, innerTextOffset: 35, dotRadius: 7 });//변경
|
||||
|
||||
gauge.init();
|
||||
|
||||
//gauge.update(initialGaugeMinutes);//기존
|
||||
gauge.update(gaugeTotalMin);//변경
|
||||
|
||||
|
||||
function saveActiveState() {
|
||||
localStorage.setItem(ACTIVE_KEY, JSON.stringify({
|
||||
myActive: activeMyKeywords,
|
||||
adminActive: activeAdminKeywords,
|
||||
}));
|
||||
}
|
||||
|
||||
async function fetchAndRefreshVideos() {
|
||||
try {
|
||||
console.log('[fetchAndRefreshVideos] activeMyKeywords=' + JSON.stringify(activeMyKeywords) + ', activeAdminKeywords=' + JSON.stringify(activeAdminKeywords));
|
||||
const res = await fetch('/edu/bbs/api/videos_by_keywords.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
my_keywords: activeMyKeywords,
|
||||
admin_keywords: activeAdminKeywords,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log('[videos_by_keywords response]', data);
|
||||
if (!data.success) {
|
||||
console.error('[videos_by_keywords] API 실패', data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
currentVideos = Array.isArray(data.videos) ? data.videos : [];
|
||||
|
||||
const apiTotalSec = Number.parseInt(data.total_watch_tm ?? data.total_all_tm, 10);
|
||||
const apiTotalMin = Number.parseInt(data.total_min, 10);
|
||||
// API 응답에서 최신 평균값 반영
|
||||
const apiAvgWatchMin = Number.parseInt(data.avg_watch_min, 10);
|
||||
if (Number.isFinite(apiAvgWatchMin) && apiAvgWatchMin > 0) {
|
||||
gaugeMaxValue = apiAvgWatchMin;
|
||||
if (gauge && gauge.config) {
|
||||
gauge.config.maxValue = gaugeMaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let gaugeMinutes = initialGaugeMinutes;
|
||||
if (Number.isFinite(apiTotalSec)) {
|
||||
gaugeMinutes = Math.floor(Math.max(0, apiTotalSec) / 60);
|
||||
} else if (Number.isFinite(apiTotalMin)) {
|
||||
gaugeMinutes = Math.max(0, apiTotalMin);
|
||||
}
|
||||
/*
|
||||
gaugeMinutes = Math.min(gaugeMaxValue, gaugeMinutes);
|
||||
gauge.update(gaugeMinutes);
|
||||
*/
|
||||
//----------------------------
|
||||
// [수정 후] 26.03.30
|
||||
// 1. 만약 내 학습시간(gaugeMinutes)이 평균(gaugeMaxValue)보다 크다면, 차트의 최대치를 내 시간에 맞춤
|
||||
if (gaugeMinutes > gaugeMaxValue) {
|
||||
gauge.config.maxValue = gaugeMinutes;
|
||||
// 차트 라이브러리에 따라 maxValue를 변경 후 다시 그리거나 init해야 할 수 있습니다.
|
||||
}
|
||||
gauge.update(gaugeMinutes);
|
||||
//----------------------------
|
||||
|
||||
|
||||
|
||||
slider = new VideoSlider({
|
||||
videos: currentVideos,
|
||||
videosPerPage: 6,
|
||||
onPageChange: (pv) => renderer.renderCards(pv),
|
||||
});
|
||||
slider.init();
|
||||
renderer.renderCards(slider.getCurrentPageVideos());
|
||||
|
||||
if (modalManager) {
|
||||
modalManager.config.videos = currentVideos;
|
||||
}
|
||||
bindCardOpenFallback();
|
||||
bindWishlistEvents();
|
||||
} catch (e) {
|
||||
console.warn('[키워드 필터]', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 진입 시에도 watch_tm 기반 최신 누적값으로 게이지 갱신
|
||||
fetchAndRefreshVideos();
|
||||
|
||||
document.getElementById('myKeyword')?.addEventListener('change', (e) => {
|
||||
if (e.target.type !== 'checkbox') return;
|
||||
const lbl = e.target.closest('label.kw-allow');
|
||||
if (!lbl) return;
|
||||
const kw = lbl.textContent.trim().replace(/^#/, '').trim();
|
||||
if (e.target.checked) {
|
||||
if (!activeMyKeywords.includes(kw)) activeMyKeywords.push(kw);
|
||||
} else {
|
||||
activeMyKeywords = activeMyKeywords.filter((k) => k !== kw);
|
||||
}
|
||||
saveActiveState();
|
||||
fetchAndRefreshVideos();
|
||||
});
|
||||
|
||||
document.querySelectorAll('.kw-deny input[type="checkbox"]').forEach((cb) => {
|
||||
cb.addEventListener('change', function () {
|
||||
const adminCbs = document.querySelectorAll('.kw-deny input[type="checkbox"]');
|
||||
const checkedCount = Array.from(adminCbs).filter((c) => c.checked).length;
|
||||
if (checkedCount === 0) {
|
||||
this.checked = true;
|
||||
return;
|
||||
}
|
||||
activeAdminKeywords = Array.from(adminCbs)
|
||||
.filter((c) => c.checked)
|
||||
.map((c) => c.closest('label')?.textContent.trim().replace(/^#/, '').trim())
|
||||
.filter(Boolean);
|
||||
saveActiveState();
|
||||
fetchAndRefreshVideos();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user