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('');
+
+
+
+ 한 줄
+
+ ${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