리뷰 반영: selonly 캡처 함정 차단, 더보기/검색 재렌더 링 오류, 검색 중 접힌 컷 결과 숨김 수정
Critical: wrapOf가 ci를 몰라 '선택한 것만 보기' 중 배정 안 된(=display:none) 쌍둥이를 캡처하는 조용한 실패가 있었다. wrapOf(hlId,idx,ci)로 정확한 복사본을 찾게 하고, buildAll에서 selonly를 이중으로 꺼서 방어한다. 칩 클릭 스크롤도 같은 방식으로 고쳤다. Important: cardSection.render()가 컷 판정 없이 링을 붙여 '더보기'·검색 재렌더 때 증상이 되살아났다 — refreshSel과 같은 판정식을 render()에 직접 넣었다(패널이 아직 document에 안 붙어 있는 초기 구성 중에도 정확히 동작해야 하므로 refreshSel 재호출 대신 선택). Important: 검색이 섹션 단위로만 display를 토글해 닫힌 컷 아코디언 안의 결과가 안 보였다. 검색 중엔 그 패널의 컷을 전부 펼치고, 지우면 검색 시작 전 열림 상태로 되돌린다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d1ebfaff74
commit
058f03265c
@ -58,8 +58,13 @@ function cardEl(c,hlId,ci){
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function wrapOf(hlId,idx){
|
||||
return $("#hlbox-"+hlId+" .ccwrap[data-cidx='"+idx+"']");
|
||||
/* ci를 주면 그 컷의 복사본을 우선으로 찾는다. 안 주거나 못 찾으면 DOM에서 처음 만나는
|
||||
복사본으로 폴백(예전 동작·폴백 패널). ci를 안 주고 querySelector 하나만 쓰면 '선택한
|
||||
것만 보기'가 켜졌을 때 배정 안 된(=display:none인) 쌍둥이가 먼저 잡혀 캡처가 빈
|
||||
이미지가 되거나 스크롤이 숨은 카드로 튀는 사고가 난다 — 반드시 ci를 넘겨라. */
|
||||
function wrapOf(hlId,idx,ci){
|
||||
const q="#hlbox-"+hlId+" .ccwrap[data-cidx='"+idx+"']";
|
||||
return (ci!==undefined&&$(q+"[data-ci='"+ci+"']"))||$(q);
|
||||
}
|
||||
|
||||
/* ── 카드 섹션 — 30장씩 렌더 + 더보기 (전체 댓글을 받아도 DOM은 점진 생성) ── */
|
||||
@ -93,7 +98,14 @@ function cardSection(label,list,hlId,firstBatch,ci){
|
||||
const c=byIdx[cur[shown]];
|
||||
if(!c) continue;
|
||||
const w=cardEl(c,hlId,ci);
|
||||
if(sel[hlId]&&sel[hlId].includes(c.idx)) w.classList.add("sel");
|
||||
// refreshSel과 같은 규칙(배정 컷 없음=폴백, 있으면 이 섹션의 컷과 같을 때만 링).
|
||||
// 이 섹션의 모든 카드는 같은 ci를 공유하므로 DOM dataset을 다시 읽을 필요 없이
|
||||
// 클로저의 ci로 바로 판정한다 — '더보기'·검색 재렌더로 새로 그려질 때마다 필요하고,
|
||||
// refreshSel을 호출하는 방식은 패널이 아직 document에 안 붙어 있으면(최초 구성 중)
|
||||
// 조용히 no-op 되므로 쓰지 않는다.
|
||||
const assigned=(selCut[hlId]||{})[c.idx];
|
||||
if(sel[hlId]&&sel[hlId].includes(c.idx)&&(assigned===undefined||assigned===ci))
|
||||
w.classList.add("sel");
|
||||
grid.appendChild(w);
|
||||
}
|
||||
if(cur===list) baseShown=Math.max(baseShown,shown);
|
||||
@ -116,6 +128,22 @@ function cardSection(label,list,hlId,firstBatch,ci){
|
||||
});
|
||||
return sec;
|
||||
}
|
||||
/* 검색 중엔 닫힌 컷 안의 결과도 봐야 하므로 그 패널의 컷 아코디언을 전부 펼친다.
|
||||
검색어를 지우면 검색 시작 전에 열려 있던 컷으로 되돌린다 — 그냥 첫 컷으로 리셋하면
|
||||
사용자가 보던 컷을 잃어버린다. 컷 개념이 없는 폴백 패널은 .cutgrp 가 없어 자동 no-op. */
|
||||
function syncCutGroupsForSearch(hlId,q){
|
||||
const box=$("#hlbox-"+hlId); if(!box) return;
|
||||
const grps=box.querySelectorAll(".cutgrp"); if(!grps.length) return;
|
||||
if(q){
|
||||
if(box.dataset.presearchOpen===undefined)
|
||||
box.dataset.presearchOpen=JSON.stringify(Array.from(grps).map(g=>g.classList.contains("open")));
|
||||
grps.forEach(g=>g.classList.add("open"));
|
||||
}else if(box.dataset.presearchOpen!==undefined){
|
||||
const opens=JSON.parse(box.dataset.presearchOpen);
|
||||
grps.forEach((g,i)=>g.classList.toggle("open",!!opens[i]));
|
||||
delete box.dataset.presearchOpen;
|
||||
}
|
||||
}
|
||||
/* ── 패널 검색창 — 입력하면 그 패널의 모든 섹션이 일치 카드만 다시 그림 ── */
|
||||
function searchBar(hlId){
|
||||
SECS[hlId]=[]; // 패널 재생성 시 이전 섹션 필터 폐기
|
||||
@ -127,6 +155,7 @@ function searchBar(hlId){
|
||||
let t=null;
|
||||
function apply(){
|
||||
const q=inp.value.trim().toLowerCase();
|
||||
syncCutGroupsForSearch(hlId,q);
|
||||
(SECS[hlId]||[]).forEach(f=>f(q));
|
||||
}
|
||||
inp.addEventListener("input",()=>{clearTimeout(t);t=setTimeout(apply,150);});
|
||||
@ -294,7 +323,12 @@ function refreshSel(hlId){
|
||||
chip.title=String(c.text||"").slice(0,120);
|
||||
chip.addEventListener("click",(e)=>{ // 칩 클릭 = 카드로 스크롤
|
||||
if(e.target.classList.contains("x")) return;
|
||||
const w=wrapOf(hlId,idx);
|
||||
const ci=(selCut[hlId]||{})[idx];
|
||||
if(ci!==undefined){ // 카드가 속한 컷 아코디언을 펼쳐야 스크롤 결과가 보인다
|
||||
box.querySelectorAll(".cutgrp").forEach(g=>
|
||||
g.classList.toggle("open",parseInt(g.dataset.ci,10)===ci));
|
||||
}
|
||||
const w=wrapOf(hlId,idx,ci); // ci를 넘겨 배정된(=보이는) 복사본을 정확히 찾는다
|
||||
if(w) w.scrollIntoView({behavior:"smooth",block:"center"});
|
||||
});
|
||||
chip.querySelector(".x").addEventListener("click",()=>toggle(hlId,idx));
|
||||
@ -719,6 +753,11 @@ async function buildAll(){
|
||||
showId(hl.id); // 캡처는 보이는 상태에서
|
||||
clearSearch(hl.id); // 검색 중이면 카드가 DOM에 없어 캡처가 누락됨
|
||||
expandAllCuts(hl.id); // 접힌 컷 섹션의 카드는 빈 이미지로 캡처된다 — 전부 펼침
|
||||
// '선택한 것만 보기'가 켜진 채면 배정 안 된 쌍둥이가 display:none으로 남는다.
|
||||
// wrapOf(ci) 로 정확한 복사본을 찾도록 고쳤지만, 실패는 조용해서(빈 이미지·캡처 스킵)
|
||||
// 사용자가 CapCut에서 드래프트를 열기 전엔 모른다 — 이중 방어로 아예 꺼 버린다.
|
||||
const buildBox=$("#hlbox-"+hl.id);
|
||||
if(buildBox) buildBox.classList.remove("selonly");
|
||||
await nextFrame();
|
||||
boardSet(hl.id,"🔄 진행","", "active");
|
||||
try{
|
||||
@ -744,9 +783,12 @@ async function buildAll(){
|
||||
const cmap=selCut[hl.id]||{};
|
||||
let n=0; const sentCuts=[];
|
||||
for(const idx of sel[hl.id]){
|
||||
const w=wrapOf(hl.id,idx);
|
||||
if(!w) continue;
|
||||
const ci=cmap[idx];
|
||||
// ci를 반드시 wrapOf에 넘겨야 한다 — 안 넘기면 DOM에서 '처음 만나는' 복사본을
|
||||
// 집는데, 컷 그룹은 컷 순서로 붙으므로 예를 들어 컷3에 배정된 댓글이라도 컷1의
|
||||
// 채우기 섹션 복사본이 먼저 잡힐 수 있다(그 복사본은 배정 컷이 아니라 링도 없다).
|
||||
const w=wrapOf(hl.id,idx,ci);
|
||||
if(!w) continue;
|
||||
/* 컷 소속을 모르는 카드는 통째로 건너뛴다. 예전엔 마지막 컷으로 폴백했는데,
|
||||
_cards_by_cut 은 컷 '안에' 균등 분할하므로 그러면 4초짜리 마지막 컷에
|
||||
여러 장이 몰려 1초 미만으로 번쩍인다. 이제 모든 카드가 ci 를 갖지만 방어로 남긴다. */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user