From 65f5d4b422e3a45f37c3d162dcd57241a8dca9c9 Mon Sep 17 00:00:00 2001 From: gm Date: Tue, 2 Jun 2026 23:59:41 +0900 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=EC=96=B4=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=ED=8A=B8=20=ED=82=A4=EB=A5=BC=20=EB=93=9C=EB=A1=AD?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=20=EC=84=A0=ED=83=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ATTR_KEYS 목록 정의 (lang/id/class/href/align/order/required/readonly/ hidden/weight/style + 직접 입력). 이미 추가된 키는 드롭다운에서 제외. 키는 배지 레이블로 표시, 값만 자유 입력. 직접 입력 선택 시 prompt()로 키 이름 수동 입력 지원. Co-Authored-By: Claude Sonnet 4.6 --- ui/builder.html | 81 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/ui/builder.html b/ui/builder.html index 6c0f9d5..3e5a5bd 100644 --- a/ui/builder.html +++ b/ui/builder.html @@ -112,12 +112,10 @@ input,textarea,button,select{font-family:inherit} .setting-inp:focus{border-color:var(--border-focus);background:#fff} .attrs-hdr{display:flex;align-items:center;gap:5px;margin-bottom:4px} .attrs-lbl{font-size:11px;color:var(--muted);flex:1} -.add-attr-btn{font-size:10px;padding:1px 7px;border-radius:4px;border:1px solid var(--accent);background:var(--accent-bg);color:var(--accent);cursor:pointer;font-weight:600} -.add-attr-btn:hover{background:var(--accent);color:#fff} .attr-row{display:flex;align-items:center;gap:4px;margin-bottom:3px} -.attr-key{width:80px;font-size:12px;border:1px solid var(--border);border-radius:4px;padding:2px 5px;background:var(--surface-alt);outline:none} +.attr-key-lbl{font-size:11px;font-weight:600;color:var(--accent);background:var(--accent-bg);border:1px solid var(--accent);border-radius:4px;padding:2px 7px;white-space:nowrap;max-width:150px;overflow:hidden;text-overflow:ellipsis;flex-shrink:0} .attr-val{flex:1;font-size:12px;border:1px solid var(--border);border-radius:4px;padding:2px 5px;background:var(--surface-alt);outline:none} -.attr-key:focus,.attr-val:focus{border-color:var(--border-focus);background:#fff} +.attr-val:focus{border-color:var(--border-focus);background:#fff} /* ── 미리보기 패널 ────────────────────────── */ .preview-panel{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--dk)} @@ -252,6 +250,21 @@ const SETTING_OPTIONS = [ {v:'attributes', lbl:'어트리뷰트'}, ]; +const ATTR_KEYS = [ + {v:'lang', lbl:'lang — 언어'}, + {v:'id', lbl:'id — 식별자'}, + {v:'class', lbl:'class — 분류'}, + {v:'href', lbl:'href — 링크'}, + {v:'align', lbl:'align — 정렬'}, + {v:'order', lbl:'order — 순서'}, + {v:'required', lbl:'required — 필수'}, + {v:'readonly', lbl:'readonly — 읽기전용'}, + {v:'hidden', lbl:'hidden — 숨김'}, + {v:'weight', lbl:'weight — 가중치'}, + {v:'style', lbl:'style — 스타일'}, + {v:'_custom', lbl:'직접 입력...'}, +]; + const SEP_PRESETS = [ {v:' ', lbl:'공백'}, {v:', ', lbl:', '}, @@ -431,19 +444,24 @@ function setSettingVal(nid, key, val){ } // ── 어트리뷰트 ───────────────────────────────────────── -function addAttr(nid){ - const n=nodeMap[nid]; if(!n||!n.settings.attributes) return; - const k=Object.keys(n.settings.attributes).includes('')? '_'+Date.now() : ''; - n.settings.attributes[k]=''; refresh(); - setTimeout(()=>{ - const rows=document.querySelectorAll(`[data-nid="${nid}"] .attr-key`); - if(rows.length) rows[rows.length-1].focus(); - },30); +function handleAddAttr(nid, key){ + if(!key) return; + if(key==='_custom'){ + const k=(prompt('어트리뷰트 키 이름 입력:')??'').trim(); + if(k) addAttr(nid, k); + } else { + addAttr(nid, key); + } } -function setAttrKey(nid, old, neo){ - const n=nodeMap[nid]; if(!n||!n.settings.attributes||old===neo) return; - const v=n.settings.attributes[old]; delete n.settings.attributes[old]; - n.settings.attributes[neo]=v; updatePreview(); save(); +function addAttr(nid, key){ + const n=nodeMap[nid]; if(!n||!n.settings.attributes||key in n.settings.attributes) return; + n.settings.attributes[key]=''; refresh(); + setTimeout(()=>{ + const inp=document.querySelector(`[data-attrnode="${nid}"][data-attrkey] .attr-val`); + // focus the last added attr-val + const all=document.querySelectorAll(`[data-attrnode="${nid}"] .attr-val`); + if(all.length) all[all.length-1].focus(); + },40); } function setAttrVal(nid, key, val){ const n=nodeMap[nid]; if(!n||!n.settings.attributes) return; @@ -601,20 +619,29 @@ function renderValue(nid, val){ function renderSetting(nid, key, val){ if(key==='attributes'){ const attrs=val??{}; - const rows=Object.entries(attrs).map(([k,v])=>` -
- - - -
`).join(''); + const usedKeys=Object.keys(attrs); + const availKeys=ATTR_KEYS.filter(o=>o.v==='_custom'||!usedKeys.includes(o.v)); + const attrKeyOpts=availKeys.map(o=>``).join(''); + const rows=Object.entries(attrs).map(([k,v])=>{ + const def=ATTR_KEYS.find(o=>o.v===k); + const lbl=def?def.lbl:k; + return ` +
+ ${esc(lbl)} + + +
`; + }).join(''); return ` -
+
어트리뷰트 - - + +
${rows||'
없음
'}
`;