feat(comment-cards): 제외 단어 필터 추가

입력한 단어가 포함된 댓글을 카드·분석 양쪽에서 제외.
쉼표/공백 다중 지정, 대량 댓글 대비 입력 디바운스(250ms).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-07-08 16:07:40 +09:00
parent 28b072df42
commit 528b48280c
2 changed files with 31 additions and 4 deletions

View File

@ -49,6 +49,23 @@
return null; return null;
} }
// 제외 단어: 쉼표/공백으로 구분. 하나라도 포함된 댓글은 카드·분석 모두에서 제외.
function getExcludeTerms() {
const raw = ($('ccExclude') && $('ccExclude').value) || '';
return raw.split(/[,\s]+/).map(s => s.trim().toLowerCase()).filter(Boolean);
}
function excludeComments(list, terms) {
if (!terms || !terms.length) return list;
return list.filter(c => {
const t = toPlainText(c.text).toLowerCase();
return !terms.some(term => t.includes(term));
});
}
function debounce(fn, ms) {
let h = null;
return function () { clearTimeout(h); h = setTimeout(fn, ms); };
}
function applyFilterSort(cards) { function applyFilterSort(cards) {
const sort = $('ccSort').value; const sort = $('ccSort').value;
const minLikes = parseInt($('ccMinLikes').value, 10) || 0; const minLikes = parseInt($('ccMinLikes').value, 10) || 0;
@ -67,7 +84,8 @@
window.renderCards = function () { window.renderCards = function () {
const grid = $('cardGrid'); const grid = $('cardGrid');
let list = applyFilterSort(window.__all); const base = excludeComments(window.__all, getExcludeTerms());
let list = applyFilterSort(base);
const total = list.length; const total = list.length;
const capped = total > DISPLAY_CAP; const capped = total > DISPLAY_CAP;
if (capped) list = list.slice(0, DISPLAY_CAP); if (capped) list = list.slice(0, DISPLAY_CAP);
@ -226,7 +244,9 @@
function renderAnalytics(all) { function renderAnalytics(all) {
const box = $('ccAnalytics'); const box = $('ccAnalytics');
if (!all || !all.length) { box.style.display = 'none'; box.innerHTML = ''; return; } const exTerms = getExcludeTerms();
all = excludeComments(all || [], exTerms);
if (!all.length) { box.style.display = 'none'; box.innerHTML = ''; return; }
const weighted = !!($('ccWeighted') && $('ccWeighted').checked); const weighted = !!($('ccWeighted') && $('ccWeighted').checked);
const tlSort = ($('ccTlSort') && $('ccTlSort').value) || 'count'; const tlSort = ($('ccTlSort') && $('ccTlSort').value) || 'count';
@ -237,7 +257,8 @@
const vid = window.__videoId; const vid = window.__videoId;
let html = '<div class="cc-an-head"><b>📊 분석</b> ' let html = '<div class="cc-an-head"><b>📊 분석</b> '
+ '<span class="cc-an-sub">전체 ' + all.length.toLocaleString() + '개 댓글 기준</span>' + '<span class="cc-an-sub">' + all.length.toLocaleString() + '개 댓글 기준'
+ (exTerms.length ? ' · 제외어 적용' : '') + '</span>'
+ '<button type="button" class="cc-iconbtn" id="ccAnToggle">접기</button></div>' + '<button type="button" class="cc-iconbtn" id="ccAnToggle">접기</button></div>'
+ '<div id="ccAnBody">'; + '<div id="ccAnBody">';
@ -434,6 +455,11 @@
$('ccUrl').addEventListener('keydown', (e) => { if (e.key === 'Enter') fetchComments(); }); $('ccUrl').addEventListener('keydown', (e) => { if (e.key === 'Enter') fetchComments(); });
['ccSearch', 'ccSort', 'ccMinLikes', 'ccRepliesOnly'].forEach(id => ['ccSearch', 'ccSort', 'ccMinLikes', 'ccRepliesOnly'].forEach(id =>
$(id).addEventListener('input', () => window.renderCards())); $(id).addEventListener('input', () => window.renderCards()));
// 제외 단어는 카드+분석 둘 다 갱신. 대량 댓글 대비 디바운스.
$('ccExclude').addEventListener('input', debounce(() => {
window.renderCards();
renderAnalytics(window.__all);
}, 250));
$('ccMosaic').addEventListener('click', () => { $('ccMosaic').addEventListener('click', () => {
mosaicOn = !mosaicOn; mosaicOn = !mosaicOn;
$('ccMosaic').textContent = mosaicOn ? '모자이크 해제' : '전체 모자이크'; $('ccMosaic').textContent = mosaicOn ? '모자이크 해제' : '전체 모자이크';

View File

@ -93,6 +93,7 @@
<div class="cc-toolbar" id="ccFilters" style="display:none;"> <div class="cc-toolbar" id="ccFilters" style="display:none;">
<input type="text" id="ccSearch" placeholder="🔍 댓글 내용 검색" style="min-width:180px;" /> <input type="text" id="ccSearch" placeholder="🔍 댓글 내용 검색" style="min-width:180px;" />
<input type="text" id="ccExclude" placeholder="🚫 제외 단어 (쉼표로 구분)" style="min-width:180px;" />
<select id="ccSort"> <select id="ccSort">
<option value="likes">좋아요순</option> <option value="likes">좋아요순</option>
<option value="replies">답글순</option> <option value="replies">답글순</option>
@ -116,7 +117,7 @@
<th:block layout:fragment="script"> <th:block layout:fragment="script">
<!-- DOM→이미지 캡처 (Task 5에서 사용). blur 필터 지원 위해 modern-screenshot 사용. --> <!-- DOM→이미지 캡처 (Task 5에서 사용). blur 필터 지원 위해 modern-screenshot 사용. -->
<script src="https://cdn.jsdelivr.net/npm/modern-screenshot@4/dist/index.js"></script> <script src="https://cdn.jsdelivr.net/npm/modern-screenshot@4/dist/index.js"></script>
<script th:src="@{/js/comment-cards.js(v=20260708b)}"></script> <script th:src="@{/js/comment-cards.js(v=20260708c)}"></script>
</th:block> </th:block>
</body> </body>
</html> </html>