Compare commits
3 Commits
7dddbc4764
...
22fcd505f4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22fcd505f4 | ||
|
|
c2ee81d202 | ||
|
|
b6fb4dadff |
@@ -86,6 +86,18 @@
|
|||||||
- Revert:
|
- Revert:
|
||||||
- `git revert f6d8ab1`
|
- `git revert f6d8ab1`
|
||||||
|
|
||||||
|
### API Elementor save/publish
|
||||||
|
- Commit: `b6fb4da`
|
||||||
|
- Objetivo: agregar endpoint dedicado `/api/elementor/save` para guardar builder con opcion de publicar.
|
||||||
|
- Revert:
|
||||||
|
- `git revert b6fb4da`
|
||||||
|
|
||||||
|
### Builder persistencia y feedback de publicacion
|
||||||
|
- Commit: `c2ee81d`
|
||||||
|
- Objetivo: mantener bloques cargados al entrar, normalizar bloques sin id y mostrar estado de guardado/publicacion en topbar.
|
||||||
|
- Revert:
|
||||||
|
- `git revert c2ee81d`
|
||||||
|
|
||||||
## URL local canonica (unificada)
|
## URL local canonica (unificada)
|
||||||
- Base local: `http://127.0.0.1:5001`
|
- Base local: `http://127.0.0.1:5001`
|
||||||
- Builder local: `http://127.0.0.1:5001/elementor/1`
|
- Builder local: `http://127.0.0.1:5001/elementor/1`
|
||||||
|
|||||||
106
elementor/routes.py
Normal file
106
elementor/routes.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
from flask import Blueprint, render_template, session, request, jsonify
|
||||||
|
import json
|
||||||
|
from db import get_db
|
||||||
|
from utils.theme_engine import get_theme_config
|
||||||
|
|
||||||
|
|
||||||
|
elementor_bp = Blueprint(
|
||||||
|
'elementor',
|
||||||
|
__name__,
|
||||||
|
template_folder='templates',
|
||||||
|
static_folder='static',
|
||||||
|
static_url_path='/elementor/static'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _render_builder(site_id, builder_mode='default', **_kwargs):
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute('SELECT user_id, slug, theme, content_json FROM sites WHERE id = ?', (site_id,))
|
||||||
|
site = c.fetchone()
|
||||||
|
|
||||||
|
if not site:
|
||||||
|
conn.close()
|
||||||
|
return 'Sitio no encontrado', 404
|
||||||
|
|
||||||
|
if 'user_id' in session and site[0] != session['user_id']:
|
||||||
|
conn.close()
|
||||||
|
return 'No autorizado', 403
|
||||||
|
|
||||||
|
content = json.loads(site[3]) if site[3] else {}
|
||||||
|
if not isinstance(content, dict):
|
||||||
|
content = {}
|
||||||
|
|
||||||
|
theme = site[2]
|
||||||
|
theme_config = get_theme_config(theme)
|
||||||
|
|
||||||
|
c.execute('SELECT plan, rubro FROM users WHERE id = ?', (site[0],))
|
||||||
|
user_data = c.fetchone()
|
||||||
|
conn.close()
|
||||||
|
user_plan = user_data[0] if user_data else 'base'
|
||||||
|
user_rubro = user_data[1] if user_data else 'restaurante'
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'elementor_builder.html',
|
||||||
|
site_id=site_id,
|
||||||
|
slug=site[1],
|
||||||
|
theme=theme,
|
||||||
|
content=content,
|
||||||
|
theme_config=theme_config,
|
||||||
|
user_plan=user_plan,
|
||||||
|
rubro=user_rubro,
|
||||||
|
builder_mode=builder_mode
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@elementor_bp.route('/elementor/<int:site_id>')
|
||||||
|
def elementor_view(site_id):
|
||||||
|
return _render_builder(site_id, builder_mode='default')
|
||||||
|
|
||||||
|
|
||||||
|
@elementor_bp.route('/ub24/<int:site_id>')
|
||||||
|
def ub24_view(site_id):
|
||||||
|
return _render_builder(site_id, builder_mode='ub24')
|
||||||
|
|
||||||
|
|
||||||
|
@elementor_bp.route('/api/elementor/save', methods=['POST'])
|
||||||
|
def save_elementor():
|
||||||
|
data = request.get_json(silent=True) or {}
|
||||||
|
site_id = data.get('site_id')
|
||||||
|
content = data.get('content')
|
||||||
|
publish = bool(data.get('publish'))
|
||||||
|
|
||||||
|
if not site_id or not isinstance(content, dict):
|
||||||
|
return jsonify({'success': False, 'error': 'Payload invalido'}), 400
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute('SELECT user_id, content_json FROM sites WHERE id = ?', (site_id,))
|
||||||
|
row = c.fetchone()
|
||||||
|
if not row:
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'success': False, 'error': 'Sitio no encontrado'}), 404
|
||||||
|
|
||||||
|
owner_id = row[0]
|
||||||
|
if 'user_id' in session and session['user_id'] != owner_id:
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'success': False, 'error': 'No autorizado'}), 403
|
||||||
|
|
||||||
|
current_content = {}
|
||||||
|
try:
|
||||||
|
if row[1]:
|
||||||
|
current_content = json.loads(row[1]) or {}
|
||||||
|
except Exception:
|
||||||
|
current_content = {}
|
||||||
|
|
||||||
|
merged = dict(current_content)
|
||||||
|
merged.update(content)
|
||||||
|
|
||||||
|
if publish:
|
||||||
|
c.execute('UPDATE sites SET content_json = ?, status = ? WHERE id = ?', (json.dumps(merged), 'published', site_id))
|
||||||
|
else:
|
||||||
|
c.execute('UPDATE sites SET content_json = ? WHERE id = ?', (json.dumps(merged), site_id))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'success': True, 'published': publish})
|
||||||
@@ -19,6 +19,10 @@
|
|||||||
.block-item{background:var(--panel2);border:1px solid var(--border);padding:8px 10px;border-radius:10px;margin-bottom:8px;cursor:grab}
|
.block-item{background:var(--panel2);border:1px solid var(--border);padding:8px 10px;border-radius:10px;margin-bottom:8px;cursor:grab}
|
||||||
.main{padding:18px}
|
.main{padding:18px}
|
||||||
.topbar{display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap}
|
.topbar{display:flex;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap}
|
||||||
|
.save-status{font-size:12px;color:var(--muted);min-width:150px;text-align:right}
|
||||||
|
.save-status.ok{color:#34d399}
|
||||||
|
.save-status.error{color:#f87171}
|
||||||
|
.save-status.busy{color:#fbbf24}
|
||||||
.btn{background:var(--accent);color:#09121a;border:0;padding:8px 12px;border-radius:999px;font-weight:700;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease}
|
.btn{background:var(--accent);color:#09121a;border:0;padding:8px 12px;border-radius:999px;font-weight:700;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease}
|
||||||
.btn:hover{transform:translateY(-1px);box-shadow:0 10px 22px rgba(15,23,42,.15)}
|
.btn:hover{transform:translateY(-1px);box-shadow:0 10px 22px rgba(15,23,42,.15)}
|
||||||
.btn.secondary{background:transparent;color:var(--text);border:1px solid var(--border)}
|
.btn.secondary{background:transparent;color:var(--text);border:1px solid var(--border)}
|
||||||
@@ -253,6 +257,7 @@
|
|||||||
<button class="btn secondary" id="btnFreeDrag" style="display:none">Modo libre</button>
|
<button class="btn secondary" id="btnFreeDrag" style="display:none">Modo libre</button>
|
||||||
<button class="btn secondary" id="btnAlign">Alinear</button>
|
<button class="btn secondary" id="btnAlign">Alinear</button>
|
||||||
<button class="btn" id="btnSave">Publicar</button>
|
<button class="btn" id="btnSave">Publicar</button>
|
||||||
|
<div class="save-status" id="saveStatus">Listo</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-shell">
|
<div class="preview-shell">
|
||||||
@@ -447,6 +452,7 @@ const state = {
|
|||||||
let pendingMove = null;
|
let pendingMove = null;
|
||||||
let pendingResize = null;
|
let pendingResize = null;
|
||||||
let previewStateBefore = null;
|
let previewStateBefore = null;
|
||||||
|
let isSaving = false;
|
||||||
|
|
||||||
function makeId(){ return "block_" + Date.now() + "_" + Math.floor(Math.random()*1000); }
|
function makeId(){ return "block_" + Date.now() + "_" + Math.floor(Math.random()*1000); }
|
||||||
function getDefaultPos(){
|
function getDefaultPos(){
|
||||||
@@ -1657,19 +1663,64 @@ const state = {
|
|||||||
});
|
});
|
||||||
renderPreview();
|
renderPreview();
|
||||||
}
|
}
|
||||||
|
function setSaveStatus(msg, kind=""){
|
||||||
|
const status = document.getElementById("saveStatus");
|
||||||
|
if (!status) return;
|
||||||
|
status.textContent = msg;
|
||||||
|
status.className = `save-status${kind ? ` ${kind}` : ""}`;
|
||||||
|
}
|
||||||
|
function normalizeLoadedBlocks(blocks){
|
||||||
|
if (!Array.isArray(blocks)) return [];
|
||||||
|
return blocks
|
||||||
|
.filter((b)=>b && typeof b === "object")
|
||||||
|
.map((b)=>({
|
||||||
|
...b,
|
||||||
|
id: b.id || makeId(),
|
||||||
|
data: (b.data && typeof b.data === "object") ? b.data : {}
|
||||||
|
}));
|
||||||
|
}
|
||||||
async function saveContent(){
|
async function saveContent(){
|
||||||
const payload={ site_id: SITE_ID, content: { ...SERVER_CONTENT, settings: state.settings, blocks: state.blocks } };
|
if (isSaving) return;
|
||||||
try{ const res=await fetch("/api/customizer/save",{ method:"POST", headers:{ "Content-Type":"application/json" }, body: JSON.stringify(payload) }); const data=await res.json(); if (!data.success) throw new Error("save failed"); alert("Cambios guardados"); }
|
isSaving = true;
|
||||||
catch(err){ console.error(err); alert("No se pudo guardar"); }
|
const btn = document.getElementById("btnSave");
|
||||||
|
if (btn){
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = "Publicando...";
|
||||||
|
}
|
||||||
|
setSaveStatus("Guardando cambios...", "busy");
|
||||||
|
const payload = {
|
||||||
|
site_id: SITE_ID,
|
||||||
|
publish: true,
|
||||||
|
content: { ...SERVER_CONTENT, settings: state.settings, blocks: state.blocks }
|
||||||
|
};
|
||||||
|
try{
|
||||||
|
const res = await fetch("/api/elementor/save",{
|
||||||
|
method:"POST",
|
||||||
|
headers:{ "Content-Type":"application/json" },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok || !data.success) throw new Error(data.error || "save failed");
|
||||||
|
setSaveStatus("Publicado", "ok");
|
||||||
|
} catch(err){
|
||||||
|
console.error(err);
|
||||||
|
setSaveStatus("Error al publicar", "error");
|
||||||
|
} finally {
|
||||||
|
isSaving = false;
|
||||||
|
if (btn){
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = "Publicar";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function init(){
|
function init(){
|
||||||
|
state.blocks = normalizeLoadedBlocks(state.blocks);
|
||||||
if (BUILDER_MODE === "ub24"){
|
if (BUILDER_MODE === "ub24"){
|
||||||
state.blocks.forEach(b=>{ if (!b.page) b.page = "home"; });
|
state.blocks.forEach(b=>{ if (!b.page) b.page = "home"; });
|
||||||
}
|
}
|
||||||
if (BUILDER_MODE === "ub24"){
|
if (BUILDER_MODE === "ub24"){
|
||||||
state.settings.free_drag = false;
|
state.settings.free_drag = false;
|
||||||
}
|
}
|
||||||
state.blocks = [];
|
|
||||||
selectedBlockId = null;
|
selectedBlockId = null;
|
||||||
wireSidebar(); wirePreviewDrop(); wireInlineEditing(); wireSettings(); wireFreeDragToggle(); wireJumpSelect(); wirePreviewSize(); wireThemeToggle();
|
wireSidebar(); wirePreviewDrop(); wireInlineEditing(); wireSettings(); wireFreeDragToggle(); wireJumpSelect(); wirePreviewSize(); wireThemeToggle();
|
||||||
const backBtn = document.getElementById("btnBack");
|
const backBtn = document.getElementById("btnBack");
|
||||||
|
|||||||
Reference in New Issue
Block a user