189 lines
6.9 KiB
HTML
189 lines
6.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>GKACHELE Customizer</title>
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
|
<style>
|
|
body { font-family: system-ui, Arial; margin:0; background:#f0f0f1; }
|
|
.wrap { display:flex; height:100vh; }
|
|
.sidebar { width:340px; background:#fff; border-right:1px solid #ddd; padding:12px; overflow:auto }
|
|
.preview { flex:1; display:flex; flex-direction:column }
|
|
.preview-header { padding:12px; background:#fff; border-bottom:1px solid #ddd }
|
|
.preview-body { padding:20px; overflow:auto; }
|
|
.btn { padding:8px 12px; border-radius:4px; border:0; cursor:pointer }
|
|
.btn-primary { background:#2271b1; color:#fff }
|
|
.btn-secondary { background:#f0f0f1 }
|
|
.block-item { padding:10px; border:1px solid #e5e5e5; margin-bottom:8px; border-radius:4px; display:flex; justify-content:space-between }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
<div class="sidebar">
|
|
<h3>GKACHELE™ Customizer</h3>
|
|
<div>
|
|
<h4>Ajustes</h4>
|
|
<label>Nombre sitio<br><input id="siteName" placeholder="Mi Sitio"></label>
|
|
<label>Color primario<br><input type="color" id="colorPrimary" value="#2271b1"></label>
|
|
</div>
|
|
<hr>
|
|
<div>
|
|
<h4>Añadir Bloque</h4>
|
|
<button class="btn btn-primary" onclick="addBlock('heading')">Encabezado</button>
|
|
<button class="btn btn-primary" onclick="addBlock('paragraph')">Párrafo</button>
|
|
<button class="btn btn-primary" onclick="addBlock('image')">Imagen</button>
|
|
</div>
|
|
<hr>
|
|
<div>
|
|
<h4>Bloques</h4>
|
|
<div id="blocksList"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="preview">
|
|
<div class="preview-header">
|
|
<span id="previewTitle">Vista previa</span>
|
|
<div style="float:right">
|
|
<button class="btn btn-secondary" onclick="discardChanges()">Descartar</button>
|
|
<button class="btn btn-primary" onclick="saveChanges()">Guardar y Publicar</button>
|
|
</div>
|
|
</div>
|
|
<div class="preview-body">
|
|
<div id="previewBlocks"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Simple customizer adapted to GKACHELE backend
|
|
let state = {
|
|
siteId: (new URLSearchParams(window.location.search)).get('site_id') || null,
|
|
blocks: [],
|
|
settings: { siteName:'Mi Sitio', colorPrimary:'#2271b1' },
|
|
hasChanges: false,
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// populate from backend if editing a real site
|
|
await loadFromBackendIfAvailable();
|
|
// fallback: localStorage
|
|
loadFromLocalStorage();
|
|
renderUI();
|
|
});
|
|
|
|
function loadFromLocalStorage(){
|
|
if (!state.siteId) {
|
|
const saved = localStorage.getItem('gkachele_customizer_demo');
|
|
if (saved) {
|
|
const d = JSON.parse(saved);
|
|
state.blocks = d.blocks||state.blocks;
|
|
state.settings = {...state.settings, ...(d.settings||{})};
|
|
}
|
|
}
|
|
}
|
|
|
|
async function loadFromBackendIfAvailable(){
|
|
if (!state.siteId) return;
|
|
try{
|
|
const r = await fetch(`/api/customizer/get-content/${state.siteId}`);
|
|
const j = await r.json();
|
|
if (j && j.success) {
|
|
const c = j.content || {};
|
|
state.blocks = c.blocks || [];
|
|
state.settings = {...state.settings, ...(c.settings||{})};
|
|
state.hasChanges = false;
|
|
updateSettingsUI();
|
|
}
|
|
}catch(e){ console.warn('Backend load failed', e); }
|
|
}
|
|
|
|
function updateSettingsUI(){
|
|
document.getElementById('siteName').value = state.settings.siteName||'';
|
|
document.getElementById('colorPrimary').value = state.settings.colorPrimary||'#2271b1';
|
|
}
|
|
|
|
function renderUI(){
|
|
updateSettingsUI();
|
|
renderBlocksList();
|
|
renderPreview();
|
|
}
|
|
|
|
function addBlock(type){
|
|
const b = { id:'b_'+Date.now(), type:type, data: getDefault(type) };
|
|
state.blocks.push(b);
|
|
state.hasChanges = true;
|
|
renderUI();
|
|
}
|
|
|
|
function getDefault(type){
|
|
if (type==='heading') return {text:'Título'};
|
|
if (type==='paragraph') return {text:'Párrafo de ejemplo'};
|
|
if (type==='image') return {url:'https://via.placeholder.com/600x300'};
|
|
return {};
|
|
}
|
|
|
|
function renderBlocksList(){
|
|
const el = document.getElementById('blocksList');
|
|
if (!state.blocks.length) { el.innerHTML='<div style="color:#888">No hay bloques</div>'; return; }
|
|
el.innerHTML = state.blocks.map((b,i)=>`<div class="block-item"><div>${b.type}</div><div><button onclick="editBlock(${i})">✏️</button> <button onclick="deleteBlock(${i})">🗑️</button></div></div>`).join('');
|
|
}
|
|
|
|
function renderPreview(){
|
|
const el = document.getElementById('previewBlocks');
|
|
if (!state.blocks.length) { el.innerHTML='<div style="color:#999">Añade bloques para ver la preview</div>'; return; }
|
|
el.innerHTML = state.blocks.map(b=>renderBlockHtml(b)).join('');
|
|
document.getElementById('previewTitle').textContent = state.settings.siteName || 'Vista previa';
|
|
}
|
|
|
|
function renderBlockHtml(b){
|
|
if (b.type==='heading') return `<h2>${escapeHtml(b.data.text)}</h2>`;
|
|
if (b.type==='paragraph') return `<p>${escapeHtml(b.data.text)}</p>`;
|
|
if (b.type==='image') return `<img src="${escapeHtml(b.data.url)}" style="max-width:100%;height:auto"/>`;
|
|
return `<div>${b.type}</div>`;
|
|
}
|
|
|
|
function editBlock(idx){
|
|
const b = state.blocks[idx];
|
|
const value = prompt('Editar contenido', b.type==='image'?b.data.url:b.data.text);
|
|
if (value!==null){
|
|
if (b.type==='image') b.data.url = value; else b.data.text = value;
|
|
state.hasChanges = true; renderUI();
|
|
}
|
|
}
|
|
|
|
function deleteBlock(idx){ if (confirm('Eliminar bloque?')) { state.blocks.splice(idx,1); state.hasChanges=true; renderUI(); } }
|
|
|
|
function escapeHtml(s){ if (!s) return ''; return s.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); }
|
|
|
|
function discardChanges(){ if (!state.hasChanges){ alert('No hay cambios'); return;} if (confirm('Descartar cambios?')){ state.blocks=[]; state.settings={siteName:'Mi Sitio',colorPrimary:'#2271b1'}; localStorage.removeItem('gkachele_customizer_demo'); renderUI(); state.hasChanges=false;} }
|
|
|
|
function updateSaveIndicator(status){ console.log('save:',status); }
|
|
|
|
function saveChanges(){
|
|
// collect settings from UI
|
|
state.settings.siteName = document.getElementById('siteName').value;
|
|
state.settings.colorPrimary = document.getElementById('colorPrimary').value;
|
|
|
|
const content = { blocks: state.blocks, settings: state.settings };
|
|
|
|
// save local demo
|
|
if (!state.siteId) localStorage.setItem('gkachele_customizer_demo', JSON.stringify(content));
|
|
|
|
// send to backend using API expected by server: { site_id, content }
|
|
if (!state.siteId) { alert('Guardado local (demo). Para guardar en SaaS abre el customizer con ?site_id=ID'); state.hasChanges=false; return; }
|
|
|
|
updateSaveIndicator('saving');
|
|
fetch('/api/customizer/save', {
|
|
method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({ site_id: state.siteId, content: content })
|
|
}).then(r=>r.json()).then(j=>{
|
|
if (j && j.success) { state.hasChanges=false; updateSaveIndicator('saved'); alert('Guardado en servidor'); }
|
|
else { updateSaveIndicator('error'); alert('Error guardando'); }
|
|
}).catch(e=>{ updateSaveIndicator('error'); alert('Error guardando: '+e); });
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|