feat(ui): 어트리뷰트 키를 드롭다운 선택으로 변경
ATTR_KEYS 목록 정의 (lang/id/class/href/align/order/required/readonly/ hidden/weight/style + 직접 입력). 이미 추가된 키는 드롭다운에서 제외. 키는 배지 레이블로 표시, 값만 자유 입력. 직접 입력 선택 시 prompt()로 키 이름 수동 입력 지원. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,12 +112,10 @@ input,textarea,button,select{font-family:inherit}
|
|||||||
.setting-inp:focus{border-color:var(--border-focus);background:#fff}
|
.setting-inp:focus{border-color:var(--border-focus);background:#fff}
|
||||||
.attrs-hdr{display:flex;align-items:center;gap:5px;margin-bottom:4px}
|
.attrs-hdr{display:flex;align-items:center;gap:5px;margin-bottom:4px}
|
||||||
.attrs-lbl{font-size:11px;color:var(--muted);flex:1}
|
.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-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-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)}
|
.preview-panel{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--dk)}
|
||||||
@@ -252,6 +250,21 @@ const SETTING_OPTIONS = [
|
|||||||
{v:'attributes', lbl:'어트리뷰트'},
|
{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 = [
|
const SEP_PRESETS = [
|
||||||
{v:' ', lbl:'공백'},
|
{v:' ', lbl:'공백'},
|
||||||
{v:', ', lbl:', '},
|
{v:', ', lbl:', '},
|
||||||
@@ -431,19 +444,24 @@ function setSettingVal(nid, key, val){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 어트리뷰트 ─────────────────────────────────────────
|
// ── 어트리뷰트 ─────────────────────────────────────────
|
||||||
function addAttr(nid){
|
function handleAddAttr(nid, key){
|
||||||
const n=nodeMap[nid]; if(!n||!n.settings.attributes) return;
|
if(!key) return;
|
||||||
const k=Object.keys(n.settings.attributes).includes('')? '_'+Date.now() : '';
|
if(key==='_custom'){
|
||||||
n.settings.attributes[k]=''; refresh();
|
const k=(prompt('어트리뷰트 키 이름 입력:')??'').trim();
|
||||||
setTimeout(()=>{
|
if(k) addAttr(nid, k);
|
||||||
const rows=document.querySelectorAll(`[data-nid="${nid}"] .attr-key`);
|
} else {
|
||||||
if(rows.length) rows[rows.length-1].focus();
|
addAttr(nid, key);
|
||||||
},30);
|
|
||||||
}
|
}
|
||||||
function setAttrKey(nid, old, neo){
|
}
|
||||||
const n=nodeMap[nid]; if(!n||!n.settings.attributes||old===neo) return;
|
function addAttr(nid, key){
|
||||||
const v=n.settings.attributes[old]; delete n.settings.attributes[old];
|
const n=nodeMap[nid]; if(!n||!n.settings.attributes||key in n.settings.attributes) return;
|
||||||
n.settings.attributes[neo]=v; updatePreview(); save();
|
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){
|
function setAttrVal(nid, key, val){
|
||||||
const n=nodeMap[nid]; if(!n||!n.settings.attributes) return;
|
const n=nodeMap[nid]; if(!n||!n.settings.attributes) return;
|
||||||
@@ -601,20 +619,29 @@ function renderValue(nid, val){
|
|||||||
function renderSetting(nid, key, val){
|
function renderSetting(nid, key, val){
|
||||||
if(key==='attributes'){
|
if(key==='attributes'){
|
||||||
const attrs=val??{};
|
const attrs=val??{};
|
||||||
const rows=Object.entries(attrs).map(([k,v])=>`
|
const usedKeys=Object.keys(attrs);
|
||||||
<div class="attr-row">
|
const availKeys=ATTR_KEYS.filter(o=>o.v==='_custom'||!usedKeys.includes(o.v));
|
||||||
<input class="attr-key" placeholder="키" value="${esc(k)}"
|
const attrKeyOpts=availKeys.map(o=>`<option value="${esc(o.v)}">${esc(o.lbl)}</option>`).join('');
|
||||||
onblur="setAttrKey('${nid}','${esc(k)}',this.value)">
|
const rows=Object.entries(attrs).map(([k,v])=>{
|
||||||
|
const def=ATTR_KEYS.find(o=>o.v===k);
|
||||||
|
const lbl=def?def.lbl:k;
|
||||||
|
return `
|
||||||
|
<div class="attr-row" data-attrnode="${nid}" data-attrkey="${esc(k)}">
|
||||||
|
<span class="attr-key-lbl" title="${esc(k)}">${esc(lbl)}</span>
|
||||||
<input class="attr-val" placeholder="값" value="${esc(String(v??''))}"
|
<input class="attr-val" placeholder="값" value="${esc(String(v??''))}"
|
||||||
oninput="setAttrVal('${nid}','${esc(k)}',this.value)">
|
oninput="setAttrVal('${nid}','${esc(k)}',this.value)">
|
||||||
<button class="iBtn" onclick="removeAttr('${nid}','${esc(k)}')">✕</button>
|
<button class="iBtn" onclick="removeAttr('${nid}','${esc(k)}')">✕</button>
|
||||||
</div>`).join('');
|
</div>`;
|
||||||
|
}).join('');
|
||||||
return `
|
return `
|
||||||
<div data-nid="${nid}">
|
<div>
|
||||||
<div class="attrs-hdr">
|
<div class="attrs-hdr">
|
||||||
<span class="attrs-lbl">어트리뷰트</span>
|
<span class="attrs-lbl">어트리뷰트</span>
|
||||||
<button class="add-attr-btn" onclick="addAttr('${nid}')">+ 추가</button>
|
<select class="add-sel" onchange="handleAddAttr('${nid}',this.value);this.selectedIndex=0">
|
||||||
<button class="iBtn" onclick="removeSetting('${nid}','attributes')" style="margin-left:4px">✕</button>
|
<option value="">+ 추가</option>
|
||||||
|
${attrKeyOpts}
|
||||||
|
</select>
|
||||||
|
<button class="iBtn" onclick="removeSetting('${nid}','attributes')">✕</button>
|
||||||
</div>
|
</div>
|
||||||
${rows||'<div class="empty-hint" style="padding:2px 0">없음</div>'}
|
${rows||'<div class="empty-hint" style="padding:2px 0">없음</div>'}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user