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:
@@ -140,6 +140,12 @@ input,textarea,button,select{font-family:inherit}
|
||||
.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}
|
||||
|
||||
/* ── 임포트 탭 ────────────────────────────── */
|
||||
.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.open{display:flex}
|
||||
@@ -206,7 +212,14 @@ input,textarea,button,select{font-family:inherit}
|
||||
|
||||
<div class="overlay" id="importModal">
|
||||
<div class="modal">
|
||||
<h2>📥 텍스트 불러오기</h2>
|
||||
<h2>📥 불러오기</h2>
|
||||
<div class="import-tabs">
|
||||
<button class="itab active" id="itab-text" onclick="switchImportTab('text')">텍스트</button>
|
||||
<button class="itab" id="itab-json" onclick="switchImportTab('json')">JSON</button>
|
||||
</div>
|
||||
|
||||
<!-- 텍스트 탭 -->
|
||||
<div id="import-text-body">
|
||||
<p>기존 양식 텍스트를 붙여넣으면 편집기로 불러옵니다.</p>
|
||||
<div class="modal-field">
|
||||
<label>제목 구분자</label>
|
||||
@@ -218,9 +231,18 @@ input,textarea,button,select{font-family:inherit}
|
||||
</div>
|
||||
<div class="hsep"></div>
|
||||
<textarea id="importTxt" placeholder="✅예시 양식 🔹1번 항목 홍길동 홍길순 🔹2번 항목 🔸소항목 (건강) 홍길찬"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<button class="mBtn cancel" onclick="closeImport()">취소</button>
|
||||
<button class="mBtn ok" onclick="doImport()">불러오기</button>
|
||||
<button class="mBtn ok" onclick="doImportActive()">불러오기</button>
|
||||
</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(){
|
||||
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]); } }
|
||||
@@ -899,6 +931,55 @@ function openImport(){
|
||||
}
|
||||
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(){
|
||||
const raw=document.getElementById('importTxt').value.trim(); if(!raw) return;
|
||||
const titleMark=document.getElementById('importTitleMark').value||'✅';
|
||||
|
||||
Reference in New Issue
Block a user