Files

80 lines
7.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 };
})();