feat(ui): JSON → 노드 불러오기 탭 추가
불러오기 모달에 텍스트/JSON 탭 구분 추가. JSON 탭: ROOT→SECTION→LIST/VALUE 구조를 노드 트리로 복원. - SECTION: mark, content, header/footer/prefix/suffix, attributes 복원 - LIST: LIST_ITEM들을 names로 결합, category 복원 - SECTION 중첩: node-type value로 재귀 복원 - VALUE: data_type/value → scalar val 복원 - nextSep/childSep/nameNewline은 기본값으로 초기화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
103
ui/builder.html
103
ui/builder.html
@@ -140,6 +140,12 @@ input,textarea,button,select{font-family:inherit}
|
|||||||
.preview-text.dim{color:var(--dt3);font-style:italic}
|
.preview-text.dim{color:var(--dt3);font-style:italic}
|
||||||
.j-key{color:#89b4fa}.j-str{color:#a6e3a1}.j-num{color:#fab387}.j-bool{color:#f38ba8}.j-null{color:#f38ba8;font-style:italic}
|
.j-key{color:#89b4fa}.j-str{color:#a6e3a1}.j-num{color:#fab387}.j-bool{color:#f38ba8}.j-null{color:#f38ba8;font-style:italic}
|
||||||
|
|
||||||
|
/* ── 임포트 탭 ────────────────────────────── */
|
||||||
|
.import-tabs{display:flex;gap:0;margin-bottom:12px;border-bottom:2px solid var(--border)}
|
||||||
|
.itab{font-size:12px;padding:6px 16px;border:none;background:transparent;color:var(--muted);cursor:pointer;font-weight:500;border-bottom:2px solid transparent;margin-bottom:-2px;transition:all .15s}
|
||||||
|
.itab.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:700}
|
||||||
|
.itab:hover:not(.active){color:var(--text)}
|
||||||
|
|
||||||
/* ── 모달 ─────────────────────────────────── */
|
/* ── 모달 ─────────────────────────────────── */
|
||||||
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;z-index:200}
|
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);display:none;align-items:center;justify-content:center;z-index:200}
|
||||||
.overlay.open{display:flex}
|
.overlay.open{display:flex}
|
||||||
@@ -206,21 +212,37 @@ input,textarea,button,select{font-family:inherit}
|
|||||||
|
|
||||||
<div class="overlay" id="importModal">
|
<div class="overlay" id="importModal">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<h2>📥 텍스트 불러오기</h2>
|
<h2>📥 불러오기</h2>
|
||||||
<p>기존 양식 텍스트를 붙여넣으면 편집기로 불러옵니다.</p>
|
<div class="import-tabs">
|
||||||
<div class="modal-field">
|
<button class="itab active" id="itab-text" onclick="switchImportTab('text')">텍스트</button>
|
||||||
<label>제목 구분자</label>
|
<button class="itab" id="itab-json" onclick="switchImportTab('json')">JSON</button>
|
||||||
<input id="importTitleMark" style="width:60px;flex:none" value="✅">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-field">
|
|
||||||
<label>섹션 구분자들</label>
|
<!-- 텍스트 탭 -->
|
||||||
<input id="importMarks" placeholder="🔹,🔸 (쉼표 구분, L1·L2 순서)">
|
<div id="import-text-body">
|
||||||
|
<p>기존 양식 텍스트를 붙여넣으면 편집기로 불러옵니다.</p>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label>제목 구분자</label>
|
||||||
|
<input id="importTitleMark" style="width:60px;flex:none" value="✅">
|
||||||
|
</div>
|
||||||
|
<div class="modal-field">
|
||||||
|
<label>섹션 구분자들</label>
|
||||||
|
<input id="importMarks" placeholder="🔹,🔸 (쉼표 구분, L1·L2 순서)">
|
||||||
|
</div>
|
||||||
|
<div class="hsep"></div>
|
||||||
|
<textarea id="importTxt" placeholder="✅예시 양식 🔹1번 항목 홍길동 홍길순 🔹2번 항목 🔸소항목 (건강) 홍길찬"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="hsep"></div>
|
|
||||||
<textarea id="importTxt" placeholder="✅예시 양식 🔹1번 항목 홍길동 홍길순 🔹2번 항목 🔸소항목 (건강) 홍길찬"></textarea>
|
<!-- JSON 탭 -->
|
||||||
|
<div id="import-json-body" style="display:none">
|
||||||
|
<p>JSON slim 또는 JSON full 출력을 붙여넣으면 노드로 복원합니다.<br>
|
||||||
|
<span style="font-size:11px;color:var(--muted)">※ 구분자·줄바꿈 설정은 기본값으로 초기화됩니다.</span></p>
|
||||||
|
<textarea id="importJsonTxt" placeholder='{"type":"ROOT", "children":[...]}'></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="mBtn cancel" onclick="closeImport()">취소</button>
|
<button class="mBtn cancel" onclick="closeImport()">취소</button>
|
||||||
<button class="mBtn ok" onclick="doImport()">불러오기</button>
|
<button class="mBtn ok" onclick="doImportActive()">불러오기</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -890,6 +912,16 @@ function copyOutput(){
|
|||||||
// ════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════
|
||||||
// 불러오기
|
// 불러오기
|
||||||
// ════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════
|
||||||
|
let importTab='text';
|
||||||
|
|
||||||
|
function switchImportTab(tab){
|
||||||
|
importTab=tab;
|
||||||
|
document.getElementById('import-text-body').style.display=tab==='text'?'':'none';
|
||||||
|
document.getElementById('import-json-body').style.display=tab==='json'?'':'none';
|
||||||
|
document.querySelectorAll('.itab').forEach(el=>el.classList.remove('active'));
|
||||||
|
document.getElementById('itab-'+tab).classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
function openImport(){
|
function openImport(){
|
||||||
document.getElementById('importTitleMark').value=S.titleMark;
|
document.getElementById('importTitleMark').value=S.titleMark;
|
||||||
const marks=[]; function col(nodes){ for(const n of nodes){ if(n.mark&&!marks.includes(n.mark)) marks.push(n.mark); for(const v of n.values) if(v.type==='node') col([v.node]); } }
|
const marks=[]; function col(nodes){ for(const n of nodes){ if(n.mark&&!marks.includes(n.mark)) marks.push(n.mark); for(const v of n.values) if(v.type==='node') col([v.node]); } }
|
||||||
@@ -899,6 +931,55 @@ function openImport(){
|
|||||||
}
|
}
|
||||||
function closeImport(){ document.getElementById('importModal').classList.remove('open') }
|
function closeImport(){ document.getElementById('importModal').classList.remove('open') }
|
||||||
|
|
||||||
|
function doImportActive(){
|
||||||
|
if(importTab==='json') doImportJson(); else doImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── JSON 파서 ──────────────────────────────────────────
|
||||||
|
function doImportJson(){
|
||||||
|
const raw=document.getElementById('importJsonTxt').value.trim();
|
||||||
|
if(!raw) return;
|
||||||
|
let obj;
|
||||||
|
try{ obj=JSON.parse(raw); }
|
||||||
|
catch(e){ alert('JSON 파싱 오류:\n'+e.message); return; }
|
||||||
|
|
||||||
|
if(obj.type!=='ROOT'){ alert('최상위 타입이 ROOT가 아닙니다.'); return; }
|
||||||
|
|
||||||
|
S.titleMark = obj.attributes?.mark ?? '✅';
|
||||||
|
S.title = obj.attributes?.title ?? '';
|
||||||
|
S.nodes = (obj.children??[]).filter(c=>c.type==='SECTION').map(parseJsonSection);
|
||||||
|
|
||||||
|
refresh(); closeImport();
|
||||||
|
document.getElementById('importJsonTxt').value='';
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJsonSection(sec){
|
||||||
|
const mark=sec.attributes?.mark??'';
|
||||||
|
const n=mkNode(mark, sec.content??'');
|
||||||
|
|
||||||
|
if(sec.header) n.settings.header=sec.header;
|
||||||
|
if(sec.footer) n.settings.footer=sec.footer;
|
||||||
|
if(sec.prefix) n.settings.prefix=sec.prefix;
|
||||||
|
if(sec.suffix) n.settings.suffix=sec.suffix;
|
||||||
|
|
||||||
|
const filtAttrs=Object.entries(sec.attributes??{}).filter(([k])=>k!=='mark'&&k!=='level');
|
||||||
|
if(filtAttrs.length) n.settings.attributes=Object.fromEntries(filtAttrs);
|
||||||
|
|
||||||
|
for(const child of sec.children??[]){
|
||||||
|
if(child.type==='LIST'){
|
||||||
|
const names=(child.children??[]).filter(c=>c.type==='LIST_ITEM').map(c=>c.content??'').join(' ');
|
||||||
|
n.values.push(mkListVal(names, child.attributes?.category??''));
|
||||||
|
} else if(child.type==='SECTION'){
|
||||||
|
n.values.push(mkNodeVal(parseJsonSection(child)));
|
||||||
|
} else if(child.type==='VALUE'){
|
||||||
|
const sv=mkScalarVal(child.data_type??'string');
|
||||||
|
sv.data=String(child.value??'');
|
||||||
|
n.values.push(sv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
function doImport(){
|
function doImport(){
|
||||||
const raw=document.getElementById('importTxt').value.trim(); if(!raw) return;
|
const raw=document.getElementById('importTxt').value.trim(); if(!raw) return;
|
||||||
const titleMark=document.getElementById('importTitleMark').value||'✅';
|
const titleMark=document.getElementById('importTitleMark').value||'✅';
|
||||||
|
|||||||
Reference in New Issue
Block a user