From 7a7ff84a2d4be9c1c6eaf73c286365321a24434d Mon Sep 17 00:00:00 2001 From: gm Date: Mon, 1 Jun 2026 23:56:08 +0900 Subject: [PATCH] feat(ui): per-group separator and namesPerLine settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전역 namesPerLine 제거 → 그룹별 개별 설정으로 전환 - 각 이름 그룹 카드에 두 가지 옵션 추가: - 구분자: 공백 / ", " / " / " / 줄바꿈(↵) 칩 + 직접 입력 - 한 줄 N개: 0=제한없음, 줄바꿈 모드 시 자동 비활성화 - mkGrp() 헬퍼로 그룹 기본값(sep=' ', namesPerLine=0) 통일 Co-Authored-By: Claude Sonnet 4.6 --- ui/builder.html | 104 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/ui/builder.html b/ui/builder.html index 48fadbe..8440a25 100644 --- a/ui/builder.html +++ b/ui/builder.html @@ -157,6 +157,30 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;backgrou .grp-names:focus{outline:none;border-color:var(--border-focus)} .grp-hint{font-size:10px;color:var(--muted);margin-top:3px} +/* ── 그룹 옵션 (구분자 / 한 줄) ─────────────────────────── */ +.grp-opts{display:flex;flex-direction:column;gap:5px;margin-top:6px;padding-top:6px;border-top:1px dashed var(--border)} +.opt-row{display:flex;align-items:center;gap:6px} +.opt-lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);width:40px;flex-shrink:0} +.sep-chips{display:flex;gap:3px;flex-wrap:wrap} +.sep-chip{ + font-size:11px;padding:2px 8px;border-radius:4px;border:1px solid var(--border); + background:var(--surface);color:var(--muted);cursor:pointer;font-weight:500; + transition:all .1s;white-space:nowrap +} +.sep-chip:hover{border-color:var(--accent);color:var(--accent)} +.sep-chip.on{background:var(--accent);color:#fff;border-color:var(--accent)} +.sep-custom{ + width:52px;font-size:11px;border:1px solid var(--border);border-radius:4px; + padding:2px 5px;background:var(--surface);text-align:center +} +.sep-custom:focus{outline:none;border-color:var(--border-focus)} +.npl-input{ + width:46px;font-size:12px;border:1px solid var(--border);border-radius:4px; + padding:2px 5px;text-align:center;background:var(--surface) +} +.npl-input:focus{outline:none;border-color:var(--border-focus)} +.npl-input:disabled{background:var(--surface-alt);color:var(--muted)} + /* ── 섹션 하단 액션 ──────────────────────────────────────── */ .sec-actions{display:flex;gap:5px;flex-wrap:wrap;padding-top:2px} .sBtn{ @@ -266,13 +290,6 @@ input,textarea,button{font-family:inherit}
-
- 한 줄 - - 개  (0 = 제한 없음) -
@@ -325,16 +342,19 @@ input,textarea,button{font-family:inherit} // ════════════════════════════════════════════════════════════ // 상태 // ════════════════════════════════════════════════════════════ +function mkGrp(cat='',names='',sep=' ',npl=0){ + return {id:id(),cat,names,sep,namesPerLine:npl}; +} let S = { - cfg: { titleMark:'✅', marks:['🔹','🔸'], namesPerLine:0 }, + cfg: { titleMark:'✅', marks:['🔹','🔸'] }, title: '예시 양식', sections: [ { id:id(), lv:1, name:'1번 항목', - groups:[{id:id(),cat:'',names:'홍길동 홍길순 홍길영 홍길준\n홍길호 홍길윤'}], + groups:[mkGrp('','홍길동 홍길순 홍길영 홍길준\n홍길호 홍길윤')], children:[] }, { id:id(), lv:1, name:'2번 항목', groups:[], children:[ - { id:id(), lv:2, name:'2-1 항목', groups:[{id:id(),cat:'건강',names:'홍길찬 홍길만'}], children:[] }, - { id:id(), lv:2, name:'2-2 항목', groups:[{id:id(),cat:'직장',names:'홍길주'}], children:[] } + { id:id(), lv:2, name:'2-1 항목', groups:[mkGrp('건강','홍길찬 홍길만')], children:[] }, + { id:id(), lv:2, name:'2-2 항목', groups:[mkGrp('직장','홍길주')], children:[] } ]} ] }; @@ -358,8 +378,7 @@ function load(){ // 설정 // ════════════════════════════════════════════════════════════ function syncConfig(){ - S.cfg.titleMark = document.getElementById('cfgTitle').value || '✅'; - S.cfg.namesPerLine = Math.max(0, parseInt(document.getElementById('cfgNpl').value)||0); + S.cfg.titleMark = document.getElementById('cfgTitle').value || '✅'; document.getElementById('titleMarkBadge').textContent = S.cfg.titleMark; renderMarks(); renderSections(); @@ -438,7 +457,7 @@ function updateName(id,v){ const s=findSec(S.sections,id); if(s) s.name=v; updat // ════════════════════════════════════════════════════════════ function addGroup(sid){ const s=findSec(S.sections,sid); if(!s) return; - const ng={id:id(),cat:'',names:''}; + const ng=mkGrp(); s.groups.push(ng); renderSections(); updatePreview(); @@ -476,7 +495,23 @@ function renderSec(sec, idx, totalInParent){ const maxLv = S.cfg.marks.length; const canChild = sec.lv < maxLv; - const grpsHtml = sec.groups.map((g,gi)=>` + const SEP_PRESETS=[ + {v:' ', label:'공백'}, + {v:', ', label:', '}, + {v:' / ',label:'/'}, + {v:'\n', label:'↵ 줄바꿈'}, + ]; + const grpsHtml = sec.groups.map((g,gi)=>{ + const sep = g.sep ?? ' '; + const npl = g.namesPerLine ?? 0; + const isNl = sep==='\n'; + const presetMatch = SEP_PRESETS.find(p=>p.v===sep); + const sepChips = SEP_PRESETS.map(p=>` + ${p.label}`).join(''); + const customVal = presetMatch ? '' : esc(sep); + return `
카테고리 @@ -488,10 +523,27 @@ function renderSec(sec, idx, totalInParent){
-
스페이스·줄바꿈·쉼표로 이름을 구분합니다
-
`).join(''); +
+
+ 구분자 +
${sepChips}
+ +
+
+ 한 줄 + + ${isNl?'(줄바꿈 모드)':'개  (0 = 제한없음)'} +
+
+ `; + }).join(''); const childrenHtml = sec.children.map((c,ci)=>renderSec(c,ci,sec.children.length)).join(''); @@ -521,7 +573,6 @@ function renderSections(){ renderMarks(); document.getElementById('titleMarkBadge').textContent = S.cfg.titleMark; document.getElementById('cfgTitle').value = S.cfg.titleMark; - document.getElementById('cfgNpl').value = S.cfg.namesPerLine ?? 0; document.getElementById('docTitle').value = S.title; document.getElementById('secList').innerHTML = S.sections.map((s,i)=>renderSec(s,i,S.sections.length)).join(''); } @@ -545,13 +596,16 @@ function toText(){ for(const g of sec.groups){ const ns=parseNames(g.names); if(!ns.length) continue; - const pre=g.cat.trim()?`(${g.cat.trim()}) `:''; - const npl=S.cfg.namesPerLine; - if(npl>0){ + const pre = g.cat.trim()?`(${g.cat.trim()}) `:''; + const sep = g.sep??' '; + const npl = g.namesPerLine??0; + if(sep==='\n'){ + ns.forEach((n,i)=>lines.push((i===0?pre:'')+n)); + } else if(npl>0){ for(let i=0;i