'use strict'; /* ============================================================================ * 사이트 템플릿 화면 (v1.3.10) * - 생성 탭 "템플릿에서 시작" (#templateStartCard): 관리자가 사용을 허용한 템플릿을 디렉터리(업종·별도)와 * 해시태그로 찾아 미리보고, 이름을 정해 새 사이트로 시작한다 (GET /api/templates, POST /api/sites/template). * - 관리자 빌더(/builder)의 관리 탭 "템플릿으로 등록" (#tplRegisterCard): 지금 사이트를 템플릿으로 담는다 * (POST /api/admin/templates). 사용자 사용 허용·수정은 관리자 콘솔 › 템플릿 탭에서 한다. * 전역: window.TemplatesUI = { showStart, onSiteOpened, dirOptions } * ========================================================================== */ (() => { const el = (tag, cls, text) => { const e = document.createElement(tag); if (cls) e.className = cls; if (text !== undefined) e.textContent = text; return e; }; const btn = (label, cls, fn) => { const b = el('button', cls || 'ghost small-btn', label); b.type = 'button'; b.onclick = fn; return b; }; let curDir = ''; let chosen = null; let timer = null; /** 디렉터리 이름 표시: ind:음식점·카페/한식 → 음식점·카페 › 한식 (업종 이름은 번역 사전으로) */ function dirLabel(dir, tree) { if (!dir) return T('전체'); if (dir.startsWith('ind:')) return dir.slice(4).split('/').map((x) => T(x)).join(' › '); const find = (nodes) => { for (const n of nodes || []) { if (n.id === dir) return n.name; const f = find(n.children); if (f) return f; } return null; }; return find(tree && tree.custom) || dir; } /** 디렉터리 선택 상자 옵션 (관리자 등록·수정 화면에서도 쓴다) */ function dirOptions(tree, sel, value) { sel.innerHTML = ''; sel.appendChild(new Option(T('디렉터리 선택'), '')); const g1 = document.createElement('optgroup'); g1.label = T('업종'); for (const i of tree.industries) { g1.appendChild(new Option(T(i.name), i.id)); for (const m of i.children) g1.appendChild(new Option(` ${T(i.name)} › ${T(m.name)}`, m.id)); } sel.appendChild(g1); if ((tree.custom || []).length) { const g2 = document.createElement('optgroup'); g2.label = T('별도 디렉터리'); const walk = (nodes, depth) => { for (const n of nodes) { g2.appendChild(new Option(`${' '.repeat(depth)}${n.name}`, n.id)); walk(n.children || [], depth + 1); } }; walk(tree.custom, 0); sel.appendChild(g2); } if (value) sel.value = value; } // ---------- 생성 탭: 템플릿에서 시작 ---------- async function load() { const q = $('tplQ').value.trim(); let d; try { d = await api(`/api/templates?q=${encodeURIComponent(q)}&dir=${encodeURIComponent(curDir)}`); } catch (e) { $('tplList').textContent = e.message; return; } // 디렉터리 트리 (템플릿이 있는 곳만) const tr = $('tplTree'); tr.innerHTML = ''; const node = (n, depth) => { if (!n.count) return; const b = el('button', 'tpl-dir' + (curDir === n.id ? ' on' : ''), `${n.kind === 'industry' ? T(n.name) : n.name} (${n.count})`); b.type = 'button'; b.style.paddingLeft = (8 + depth * 14) + 'px'; b.onclick = () => { curDir = curDir === n.id ? '' : n.id; load(); }; tr.appendChild(b); for (const c of n.children || []) node(c, depth + 1); }; const all = el('button', 'tpl-dir' + (curDir ? '' : ' on'), `${T('전체')} (${d.tree.total})`); all.type = 'button'; all.onclick = () => { curDir = ''; load(); }; tr.appendChild(all); for (const n of d.tree.industries) node(n, 0); for (const n of d.tree.custom) node(n, 0); // 해시태그 const tg = $('tplTags'); tg.innerHTML = ''; for (const t of d.tags.slice(0, 16)) { const c = el('button', 'tpl-tag', `#${t.tag}`); c.type = 'button'; c.onclick = () => { const v = $('tplQ').value; if (!v.includes('#' + t.tag)) $('tplQ').value = (v + ' #' + t.tag).trim(); load(); }; tg.appendChild(c); } // 목록 const list = $('tplList'); list.innerHTML = ''; for (const t of d.items) { const card = el('div', 'tpl-card' + (chosen === t.id ? ' on' : '')); card.appendChild(el('b', '', t.title)); card.appendChild(el('div', 'muted small', dirLabel(t.dir, d.tree))); if (t.description) card.appendChild(el('div', 'small', t.description)); const tags = el('div', 'tpl-tags'); for (const g of t.tags) tags.appendChild(el('span', 'tpl-tag', '#' + g)); card.appendChild(tags); const row = el('div', 'row'); const pv = el('a', 'btn ghost small-btn', T('미리보기')); pv.href = `/api/templates/${t.id}/preview/`; pv.target = '_blank'; pv.rel = 'noopener'; row.appendChild(pv); row.appendChild(btn(T('이 템플릿 고르기'), chosen === t.id ? 'primary small-btn' : 'ghost small-btn', () => { chosen = t.id; $('tplChosen').textContent = TF('선택한 템플릿: {1}', t.title); $('tplStart').disabled = false; if (!$('tplTitle').value) $('tplTitle').placeholder = t.title; load(); })); card.appendChild(row); list.appendChild(card); } if (!d.items.length) list.appendChild(el('p', 'muted small', q || curDir ? T('조건에 맞는 템플릿이 없습니다.') : T('아직 사용할 수 있는 템플릿이 없습니다.'))); } function showStart() { $('templateStartCard').classList.remove('hidden'); chosen = null; $('tplStart').disabled = true; $('tplChosen').textContent = ''; load(); } $('tplQ').oninput = () => { clearTimeout(timer); timer = setTimeout(load, 250); }; $('tplStart').onclick = async () => { if (!chosen) return; $('tplStart').disabled = true; try { const site = await api('/api/sites/template', { method: 'POST', body: JSON.stringify({ templateId: chosen, title: $('tplTitle').value.trim(), contactEmail: $('tplEmail').value.trim(), tier: $('tierSelect').value || '', optimize: seoOptsOf('templateStartCard') }) }); $('tplTitle').value = ''; $('tplEmail').value = ''; await openSite(site.id); await refreshSiteList(); } catch (e) { alert(e.message); $('tplStart').disabled = false; } }; // ---------- 관리자 빌더: 이 사이트를 템플릿으로 등록 ---------- let tree = null; async function onSiteOpened(site) { const card = $('tplRegisterCard'); const admin = config && config.user && config.user.role === 'admin'; const ready = admin && site && (!site.plan || ['generated', 'published'].includes(site.plan.stage)) && (site.files || []).some((f) => /\.html?$/i.test(f)); card.classList.toggle('hidden', !ready); if (!ready) return; if (!tree) { try { tree = (await api('/api/templates')).tree; } catch (_) { return; } } dirOptions(tree, $('tplRegDir')); $('tplRegTitle').value = site.title; $('tplRegMsg').textContent = ''; } $('tplRegGo').onclick = async () => { if (!current) return; $('tplRegMsg').textContent = T('담는 중…'); try { const t = await api('/api/admin/templates', { method: 'POST', body: JSON.stringify({ siteId: current.id, title: $('tplRegTitle').value, tags: $('tplRegTags').value, dir: $('tplRegDir').value, description: $('tplRegDesc').value }) }); $('tplRegMsg').textContent = TF('템플릿 "{1}" 으로 등록했습니다 (파일 {2}개). 사용자 사용 허용은 관리자 콘솔 › 템플릿 에서 켭니다.', t.title, t.files); } catch (e) { $('tplRegMsg').textContent = e.message; } }; window.TemplatesUI = { showStart, onSiteOpened, dirOptions, dirLabel }; })();