Demo con contador de pasos e icono de detener, horas de sesión coherentes, IDs de sesión sin choques, URLs bloqueadas inertes, recortes de importación contados y sesionAGuion con deshacer
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9a5f21badf
commit
c0b7d0c2ce
+74
-12
@@ -221,10 +221,54 @@ window.WAM = window.WAM || {};
|
||||
return n;
|
||||
}
|
||||
|
||||
function lista(v, max) {
|
||||
return (Array.isArray(v) ? v : []).slice(0, max || MAX_ITEMS);
|
||||
/* Contadores de lo que recorta la normalización en curso (ver
|
||||
WAM.ultimosRecortes). null fuera de WAM.normalizar. */
|
||||
var recortes = null;
|
||||
|
||||
function lista(v, max, clave) {
|
||||
var arr = Array.isArray(v) ? v : [];
|
||||
var tope = max || MAX_ITEMS;
|
||||
if (recortes && clave && arr.length > tope) recortes[clave] += arr.length - tope;
|
||||
return arr.slice(0, tope);
|
||||
}
|
||||
|
||||
/* URL de un botón/card de enlace: si venía escrita pero es insegura se
|
||||
vacía y se marca urlBloqueada, para que siga siendo un botón de enlace
|
||||
(inerte) y no se convierta en una respuesta rápida con flujo. */
|
||||
function urlEnlace(destino, origen) {
|
||||
var bruta = cad(origen.url).trim();
|
||||
destino.url = urlLimpia(bruta, "enlace");
|
||||
if (!destino.url && (bruta || origen.urlBloqueada === true || origen.urlBloqueada === "true")) {
|
||||
destino.urlBloqueada = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Texto en español con lo recortado, o "" si no se recortó nada. */
|
||||
WAM.describirRecortes = function (r) {
|
||||
r = r || WAM.ultimosRecortes;
|
||||
if (!r) return "";
|
||||
var partes = [];
|
||||
function n(c, uno, varios) { if (r[c] > 0) partes.push(r[c] + " " + (r[c] === 1 ? uno : varios)); }
|
||||
n("botones", "botón", "botones");
|
||||
n("secciones", "sección", "secciones");
|
||||
n("filas", "fila", "filas");
|
||||
n("cards", "tarjeta", "tarjetas");
|
||||
n("respuestas", "respuesta", "respuestas");
|
||||
n("profundidad", "respuesta anidada (más de " + MAX_PROFUNDIDAD + " niveles)",
|
||||
"respuestas anidadas (más de " + MAX_PROFUNDIDAD + " niveles)");
|
||||
var txt = "";
|
||||
if (r.mensajesOriginales > r.mensajes) {
|
||||
txt = "Se importaron " + r.mensajes + " de " + r.mensajesOriginales + " mensajes";
|
||||
}
|
||||
if (partes.length) {
|
||||
var total = 0;
|
||||
["botones", "secciones", "filas", "cards", "respuestas", "profundidad"].forEach(function (c) { total += r[c] || 0; });
|
||||
var verbo = total === 1 ? "se recortó " : "se recortaron ";
|
||||
txt += (txt ? "; " + verbo : verbo.charAt(0).toUpperCase() + verbo.slice(1)) + partes.join(", ");
|
||||
}
|
||||
return txt;
|
||||
};
|
||||
|
||||
/* Rellena los campos que faltan por tipo y devuelve un estado válido.
|
||||
Usado por cargar(), resetear() y editor.importarJSON (import / upgrade).
|
||||
- Acepta también un array de mensajes suelto como raíz.
|
||||
@@ -239,6 +283,11 @@ window.WAM = window.WAM || {};
|
||||
: ((o && typeof o === "object") ? o : {});
|
||||
|
||||
entregados = new Set();
|
||||
var mensajesSrc = Array.isArray(src.mensajes) ? src.mensajes.length : 0;
|
||||
recortes = {
|
||||
mensajes: Math.min(mensajesSrc, 500), mensajesOriginales: mensajesSrc,
|
||||
botones: 0, secciones: 0, filas: 0, cards: 0, respuestas: 0, profundidad: 0
|
||||
};
|
||||
var conjunto = new Set();
|
||||
recolectarIds(src, conjunto);
|
||||
var tomar = crearAlocador(conjunto);
|
||||
@@ -270,16 +319,19 @@ window.WAM = window.WAM || {};
|
||||
};
|
||||
|
||||
function respuestas(valor) {
|
||||
if (profundidad >= MAX_PROFUNDIDAD) return [];
|
||||
return lista(valor, 20).map(function (r) { return normalizarMensaje(r, true, profundidad + 1); });
|
||||
if (profundidad >= MAX_PROFUNDIDAD) {
|
||||
if (Array.isArray(valor) && valor.length) recortes.profundidad += valor.length;
|
||||
return [];
|
||||
}
|
||||
return lista(valor, 20, "respuestas").map(function (r) { return normalizarMensaje(r, true, profundidad + 1); });
|
||||
}
|
||||
|
||||
function botones(valor) {
|
||||
return lista(valor).map(function (b) {
|
||||
return lista(valor, MAX_ITEMS, "botones").map(function (b) {
|
||||
var boton = (b && typeof b === "object") ? b : { texto: typeof b === "string" ? b : "" };
|
||||
var out = { texto: cad(boton.texto), respuestas: respuestas(boton.respuestas) };
|
||||
/* Botón URL de plantilla (research-api §7.2): se conserva si viene */
|
||||
if (typeof boton.url === "string") out.url = urlLimpia(boton.url, "enlace");
|
||||
if (typeof boton.url === "string" || boton.urlBloqueada === true) urlEnlace(out, boton);
|
||||
return out;
|
||||
});
|
||||
}
|
||||
@@ -338,11 +390,11 @@ window.WAM = window.WAM || {};
|
||||
msg.body = cad(m.body);
|
||||
msg.footer = cad(m.footer);
|
||||
msg.boton = cad(m.boton);
|
||||
msg.secciones = lista(m.secciones).map(function (s) {
|
||||
msg.secciones = lista(m.secciones, MAX_ITEMS, "secciones").map(function (s) {
|
||||
var seccion = (s && typeof s === "object") ? s : {};
|
||||
return {
|
||||
titulo: cad(seccion.titulo),
|
||||
filas: lista(seccion.filas).map(function (f) {
|
||||
filas: lista(seccion.filas, MAX_ITEMS, "filas").map(function (f) {
|
||||
var fila = (f && typeof f === "object") ? f : { titulo: typeof f === "string" ? f : "" };
|
||||
return {
|
||||
titulo: cad(fila.titulo),
|
||||
@@ -363,15 +415,17 @@ window.WAM = window.WAM || {};
|
||||
break;
|
||||
case "carousel":
|
||||
msg.body = cad(m.body);
|
||||
msg.cards = lista(m.cards).map(function (c) {
|
||||
msg.cards = lista(m.cards, MAX_ITEMS, "cards").map(function (c) {
|
||||
var card = (c && typeof c === "object") ? c : {};
|
||||
return {
|
||||
var salida = {
|
||||
media: urlLimpia(card.media, "media"),
|
||||
texto: cad(card.texto),
|
||||
textoBoton: cad(card.textoBoton),
|
||||
url: urlLimpia(card.url, "enlace"),
|
||||
url: "",
|
||||
respuestas: respuestas(card.respuestas)
|
||||
};
|
||||
urlEnlace(salida, card);
|
||||
return salida;
|
||||
});
|
||||
autoPulsar();
|
||||
break;
|
||||
@@ -389,7 +443,7 @@ window.WAM = window.WAM || {};
|
||||
}
|
||||
|
||||
var cuentaSrc = (src.cuenta && typeof src.cuenta === "object") ? src.cuenta : {};
|
||||
return {
|
||||
var resultado = {
|
||||
version: 1,
|
||||
tema: src.tema === "oscuro" ? "oscuro" : "claro",
|
||||
cuenta: {
|
||||
@@ -404,6 +458,14 @@ window.WAM = window.WAM || {};
|
||||
return normalizarMensaje(x, false, 0);
|
||||
})
|
||||
};
|
||||
/* Lo recortado queda en WAM.ultimosRecortes y, no enumerable (no se
|
||||
guarda ni se exporta), en resultado.__recortes. */
|
||||
WAM.ultimosRecortes = recortes;
|
||||
try {
|
||||
Object.defineProperty(resultado, "__recortes", { value: recortes, enumerable: false, configurable: true });
|
||||
} catch (e) { /* entorno sin defineProperty */ }
|
||||
recortes = null;
|
||||
return resultado;
|
||||
};
|
||||
|
||||
/* ————————————————— persistencia ————————————————— */
|
||||
|
||||
Reference in New Issue
Block a user