auto.js: 카드 패널 렌더러를 renderCutPanel()로 통합

onResult(자동 탭)과 ytMatch(구간 탭)에 각각 있던 검색창+컷별/ 섹션+후보
렌더 블록이 두 갈래로 갈라져 있어, 곧 붙여넣기 탭까지 더해지면 한 곳만
고치는 실수가 나기 쉬웠다. 두 블록을 renderCutPanel(box,panelId,data,opts)
하나로 합쳐 배지·검색·선택 상한 로직을 한 곳에 모았다.

화면 문구는 완전히 동일하게 보존했다 — 특히  폴백 라벨은 onResult가
"이 구간을", ytMatch가 "구간을"로 이미 서로 달랐기에, unit 값에 따라
원문 그대로 재현하도록 분기했다(무작정 unit을 문자열에 꽂지 않음).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-08-04 17:20:21 +09:00
parent aab2a3199b
commit 22a9758e74

View File

@ -137,6 +137,48 @@ function clearSearch(hlId){
const bar=box&&box.querySelector(".ccsearch");
if(bar&&bar._clear) bar._clear();
}
/* // .
갈라 두면 곳만 고치는 실수가 난다(배지·검색·선택 상한이 전부 여기 모여 있다). */
function renderCutPanel(box,panelId,data,opts){
const unit=(opts&&opts.unit)||"컷";
const WHY={ts:"⭐",ai:"🤖",like:""};
box.appendChild(searchBar(panelId)); // 카드 섹션들 위 — cardSection 보다 먼저 만들어야 SECS 초기화됨
const cand=(data.candidates||[]).filter(i=>byIdx[i]!==undefined);
if(data.cuts){
/* . :
소속(ci) 없어 빌드 곳이 없다. quota 미달분을 에서 채우는 워크플로다.
rest(추천 '나머지') 시간대를 언급한 댓글만 그러면 컷마다 전체가 중복된다. */
for(const cu of data.cuts){
const rec=(cu.picks||[]).map(p=>p.idx).filter(i=>byIdx[i]!==undefined);
const why=(cu.picks||[]).map(p=>WHY[p.why]||"").join("");
const cs=(data.cutRanges||[])[cu.i]||{start:0,end:-1};
const rest=(data.matched||[]).filter(i=>
byIdx[i]!==undefined&&!rec.includes(i)&&
(byIdx[i].times||[]).some(t=>t>=cs.start&&t<=cs.end));
box.appendChild(cardSection(
unit+" "+(cu.i+1)+" · "+cu.sec+"초 · 카드 "+cu.quota+"장"+
(cu.bottom?" — "+cu.bottom:"")+(why?" "+why:""),
rec.concat(rest),panelId,Math.max(CARD_PAGE,rec.length),cu.i));
// 부족분 채우기 — 컷마다 반복되므로 처음엔 조금만 그리고 나머지는 더보기로
if(cand.length)
box.appendChild(cardSection(
"↳ 좋아요 상위에서 채우기 "+cand.length+"장 (이 컷에 넣기)",
cand,panelId,CUT_FILL_PAGE,cu.i));
}
}else{
// 폴백 — cuts 정보가 없으면(구간 탭·구정보 없음) 예전 화면 그대로: ⭐ + 독립
// ⚠ 이 ⭐ 문구는 예전부터 "이 구간을"로 고정돼 있었다(unit="컷"이어도) — 화면 불변 원칙상 유지
const m=(data.matched||[]).filter(i=>byIdx[i]!==undefined);
box.appendChild(cardSection(
"⭐ "+(unit==="컷"?"이 구간":unit)+"을 언급한 댓글 "+m.length+"장 (좋아요순, 자동 선택)",
m,panelId,Math.max(CARD_PAGE,(sel[panelId]||[]).length)));
if(cand.length){
box.appendChild(cardSection(
" 좋아요 상위 후보 "+cand.length+"장 (부족분 클릭)",
cand,panelId,CARD_PAGE));
}
}
}
/* ' ' .
selCut[hlId][idx] = 인덱스. sel[hlId] 항상 순서로 정렬해 둔다
(업로드 순서 = 배치 순서라서). */
@ -537,47 +579,9 @@ function onResult(ev){
box.dataset.titles=JSON.stringify(opts);
// 영상 편집안(컷 목록·JSON) — 선택 요약 바 위에, 기본 접힘
box.insertBefore(cutsSection(hl),$("#hlsel-"+hl.id));
box.appendChild(searchBar(hl.id)); // 카드 섹션들 위 — cardSection 보다 먼저 만들어야 SECS 초기화됨
const WHY={ts:"⭐",ai:"🤖",like:""};
const cand=(hl.candidates||[]).filter(i=>byIdx[i]!==undefined);
if(hl.cuts){
/* . :
거기서 고른 카드는 소속(ci) 없어 빌드 곳이 없다. 그런데 AI 프롬프트가
"억지로 채우지 마라"라고 지시하므로 quota 미달이 기본이고, 미달분을 에서
채우는 워크플로다. _cards_by_cut '뒤' 아니라 '안에' 균등 분할하니
소속 없는 카드를 컷에 몰면 4 컷에 5장이 들어가 0.8초씩 번쩍인다. */
for(const cu of hl.cuts){
const rec=(cu.picks||[]).map(p=>p.idx).filter(i=>byIdx[i]!==undefined);
const why=(cu.picks||[]).map(p=>WHY[p.why]||"").join("");
/* '' ** **.
hl.matched 전체를 컷마다 복제하면 12 × 30 = 360장씩 부풀고,
같은 댓글이 여러 섹션에 중복돼 한쪽을 고르면 쌍둥이까지 하이라이트된다. */
const cs=(hl.paste.cuts||[])[cu.i]||{start:0,end:-1};
const rest=(hl.matched||[]).filter(i=>
byIdx[i]!==undefined&&!rec.includes(i)&&
(byIdx[i].times||[]).some(t=>t>=cs.start&&t<=cs.end));
const label="컷 "+(cu.i+1)+" · "+cu.sec+"초 · 카드 "+cu.quota+"장"+
(cu.bottom?" — "+cu.bottom:"")+(why?" "+why:"");
box.appendChild(cardSection(label,rec.concat(rest),hl.id,
Math.max(CARD_PAGE,rec.length),cu.i));
// 부족분 채우기 — 컷마다 반복되므로 처음엔 조금만 그리고 나머지는 더보기로
if(cand.length)
box.appendChild(cardSection(
"↳ 좋아요 상위에서 채우기 "+cand.length+"장 (이 컷에 넣기)",
cand,hl.id,CUT_FILL_PAGE,cu.i));
}
}else{
// 폴백 — hl.cuts 가 없으면(컷 정보 없음) 예전 화면 그대로: ⭐ + 독립
const m=(hl.matched||[]).filter(i=>byIdx[i]!==undefined);
box.appendChild(cardSection(
"⭐ 이 구간을 언급한 댓글 "+m.length+"장 (좋아요순, 자동 선택)",
m,hl.id,Math.max(CARD_PAGE,sel[hl.id].length)));
if(cand.length){
box.appendChild(cardSection(
" 좋아요 상위 후보 "+cand.length+"장 (부족분 클릭)",
cand,hl.id,CARD_PAGE));
}
}
renderCutPanel(box,hl.id,
{cuts:hl.cuts,matched:hl.matched,candidates:hl.candidates,
cutRanges:hl.paste.cuts},{unit:"컷"});
R.appendChild(box);
refreshSel(hl.id);
}
@ -765,15 +769,8 @@ async function ytMatch(){
'<input type="checkbox" id="ytccFixed" checked style="accent-color:var(--accent);width:14px;height:14px;">'+
' 카드 3초 고정 — 모자라도 늘리지 않고 뒤는 비움 (부분삭제 편집용)</label>'+
'<div class="selbar" id="hlsel-yt"></div>';
box.appendChild(searchBar("yt"));
box.appendChild(cardSection(
"⭐ 구간을 언급한 댓글 "+matched.length+"장 (좋아요순, 자동 선택)",
matched,"yt",Math.max(CARD_PAGE,sel["yt"].length)));
if(cand.length){
box.appendChild(cardSection(
" 좋아요 상위 후보 "+cand.length+"장 (부족분 클릭)",
cand,"yt",CARD_PAGE));
}
renderCutPanel(box,"yt",
{matched:matched,candidates:cand},{unit:"구간"});
area.appendChild(box);
refreshSel("yt");
$("#ytccNote").textContent="선택한 카드는 편집 시작 때 자동으로 들어갑니다(폴더 지정보다 우선). "