Integrar rama de lógica: contador de pasos, horas coherentes, IDs de sesión y URLs bloqueadas
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -51,6 +51,8 @@ con `dir:"in"`, anidados, NO se renderizan en el chat: los consume la interactiv
|
|||||||
Convenciones: `src`/`avatar`/`headerMedia`/`media` = dataURL o URL remota; `""` → placeholder SVG
|
Convenciones: `src`/`avatar`/`headerMedia`/`media` = dataURL o URL remota; `""` → placeholder SVG
|
||||||
de `WAM.svgPlaceholder(texto)`. Botones con `url` no vacía se renderizan como `<a target="_blank">`
|
de `WAM.svgPlaceholder(texto)`. Botones con `url` no vacía se renderizan como `<a target="_blank">`
|
||||||
(cta_url, template URL, card de carousel); sin `url` → flujo interactivo con `respuestas`.
|
(cta_url, template URL, card de carousel); sin `url` → flujo interactivo con `respuestas`.
|
||||||
|
Botón de plantilla o card con URL no permitida: `normalizar` deja `url: ""` y añade `urlBloqueada: true`
|
||||||
|
(sigue siendo un botón de enlace, inerte; nunca un quick reply con flujo; `resolverFlujo` lo ignora).
|
||||||
`WAM.normalizar(o)` rellena campos faltantes por tipo y devuelve el estado válido (import/upgrade).
|
`WAM.normalizar(o)` rellena campos faltantes por tipo y devuelve el estado válido (import/upgrade).
|
||||||
Campo opcional `autoPulsar` (entero ≥ 0) en `template`/`buttons`/`list`/`carousel` (también en respuestas
|
Campo opcional `autoPulsar` (entero ≥ 0) en `template`/`buttons`/`list`/`carousel` (también en respuestas
|
||||||
anidadas): índice del botón, de la fila aplanada de la lista o de la card que «Reproducir demo» pulsa sola
|
anidadas): índice del botón, de la fila aplanada de la lista o de la card que «Reproducir demo» pulsa sola
|
||||||
@@ -73,7 +75,12 @@ cancela demo y flujos (interactivity.js envuelve `WAM.render`).
|
|||||||
(main.js lo pinta con `WAM.avisar`) hasta el siguiente guardado correcto.
|
(main.js lo pinta con `WAM.avisar`) hasta el siguiente guardado correcto.
|
||||||
- `WAM.resetear()` → void (borra LS, estado = demo con IDs desde `m1`)
|
- `WAM.resetear()` → void (borra LS, estado = demo con IDs desde `m1`)
|
||||||
- `WAM.normalizar(o)` → estado (acepta un array de mensajes como raíz; IDs duplicados o inválidos se
|
- `WAM.normalizar(o)` → estado (acepta un array de mensajes como raíz; IDs duplicados o inválidos se
|
||||||
reasignan; vacía URLs con esquema no permitido; respuestas string → text; conserva campos extra primitivos)
|
reasignan; vacía URLs con esquema no permitido; respuestas string → text; conserva campos extra primitivos).
|
||||||
|
Lo recortado (más de 500 mensajes, 10 botones/secciones/filas/cards, 20 respuestas o 12 niveles) queda en
|
||||||
|
`WAM.ultimosRecortes` y en `estado.__recortes` (no enumerable): `{ mensajes, mensajesOriginales, botones,
|
||||||
|
secciones, filas, cards, respuestas, profundidad }`.
|
||||||
|
- `WAM.describirRecortes(r?)` → string en español para el toast de importar («Se importaron 500 de 600
|
||||||
|
mensajes; se recortaron 3 respuestas…») o `""` si no se recortó nada (por defecto `WAM.ultimosRecortes`)
|
||||||
- `WAM.generarId()` → string (nunca repite un ID ya entregado aunque aún no esté en el estado)
|
- `WAM.generarId()` → string (nunca repite un ID ya entregado aunque aún no esté en el estado)
|
||||||
- `WAM.crearAlocador(extra?)` → `function tomar()` que reparte IDs únicos frente al estado, a los ya
|
- `WAM.crearAlocador(extra?)` → `function tomar()` que reparte IDs únicos frente al estado, a los ya
|
||||||
entregados y a los de `extra` (para clonar mensajes con respuestas anidadas)
|
entregados y a los de `extra` (para clonar mensajes con respuestas anidadas)
|
||||||
@@ -168,9 +175,15 @@ cancela demo y flujos (interactivity.js envuelve `WAM.render`).
|
|||||||
" + descripcion`.
|
" + descripcion`.
|
||||||
- `WAM.mensajeDeElemento(el)` → Mensaje · `WAM.abrirLista(msg)` → abre la hoja (`abrirSheet(i | msg)`)
|
- `WAM.mensajeDeElemento(el)` → Mensaje · `WAM.abrirLista(msg)` → abre la hoja (`abrirSheet(i | msg)`)
|
||||||
- `WAM.marcarUsado(el)` → clase `.usado` (gris, sin más clics) · `WAM.reiniciarChat()` (= `render()`)
|
- `WAM.marcarUsado(el)` → clase `.usado` (gris, sin más clics) · `WAM.reiniciarChat()` (= `render()`)
|
||||||
- `WAM.sesionAGuion()` → n: añade la sesión al guion (acción explícita) + guardar + re-render
|
- `WAM.sesionAGuion()` → n: añade la sesión al guion (acción explícita) + `puntoDeshacer` + guardar +
|
||||||
|
`marcarEditado` + re-render
|
||||||
|
- Hora de la sesión: las salientes de un flujo y sus respuestas usan la hora del último mensaje visible
|
||||||
|
+ 1 min (línea temporal coherente con el guion); el composer libre usa `WAM.horaAhora()`. Los IDs de sesión
|
||||||
|
(`s1`, `s2`…) saltan los que ya existen en el guion.
|
||||||
- `WAM.getVelocidad()` / `WAM.setVelocidad(0.5|1|2)` (persistida en `wam-mockup-v1-velocidad`)
|
- `WAM.getVelocidad()` / `WAM.setVelocidad(0.5|1|2)` (persistida en `wam-mockup-v1-velocidad`)
|
||||||
- `WAM.demoActiva()` → bool · hook opcional `WAM.onDemoChange(activa)`
|
- `WAM.demoActiva()` → bool · hook opcional `WAM.onDemoChange(activa)` (también se llama en cada paso)
|
||||||
|
- `WAM.progresoDemo()` → `{ paso, total }` (mensajes del guion revelados / total; `{0, 0}` sin demo).
|
||||||
|
main.js lo pinta en `#demo-paso` («Paso 3/12», `aria-live`) dentro de `#barra-movil`
|
||||||
- `WAM.reproducirDemo()` / `WAM.detenerDemo()` → void: cancela flujos en curso, vacía `#chat-cuerpo` y revela
|
- `WAM.reproducirDemo()` / `WAM.detenerDemo()` → void: cancela flujos en curso, vacía `#chat-cuerpo` y revela
|
||||||
`estado.mensajes` uno a uno (pulsando solas las opciones `autoPulsar`); detener vuelve al guion
|
`estado.mensajes` uno a uno (pulsando solas las opciones `autoPulsar`); detener vuelve al guion
|
||||||
(~600 ms; mensaje `in` precedido de typing ~700 ms; cancelable)
|
(~600 ms; mensaje `in` precedido de typing ~700 ms; cancelable)
|
||||||
|
|||||||
+56
-8
@@ -27,7 +27,42 @@ window.WAM = window.WAM || {};
|
|||||||
/* Mensajes de la sesión en curso (efímeros). */
|
/* Mensajes de la sesión en curso (efímeros). */
|
||||||
WAM.sesion = [];
|
WAM.sesion = [];
|
||||||
var contSesion = 0;
|
var contSesion = 0;
|
||||||
function idSesion() { contSesion++; return "s" + contSesion; }
|
|
||||||
|
/* IDs del guion (mensajes, respuestas, botones, filas, cards) para que un
|
||||||
|
ID de sesión nunca coincida con uno importado (p. ej. un guion con "s1"). */
|
||||||
|
function idsDelGuion() {
|
||||||
|
var usados = new Set();
|
||||||
|
(function recorrer(o) {
|
||||||
|
if (!o || typeof o !== "object") return;
|
||||||
|
if (Array.isArray(o)) { for (var i = 0; i < o.length; i++) recorrer(o[i]); return; }
|
||||||
|
if (typeof o.id === "string" && o.id) usados.add(o.id);
|
||||||
|
["mensajes", "respuestas", "botones", "secciones", "filas", "cards"].forEach(function (k) {
|
||||||
|
if (o[k] && typeof o[k] === "object") recorrer(o[k]);
|
||||||
|
});
|
||||||
|
})(WAM.estado);
|
||||||
|
return usados;
|
||||||
|
}
|
||||||
|
|
||||||
|
function idSesion() {
|
||||||
|
var usados = WAM.estado ? idsDelGuion() : null;
|
||||||
|
var id;
|
||||||
|
do { contSesion++; id = "s" + contSesion; } while (usados && usados.has(id));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hora para los mensajes de un flujo: la del último mensaje visible + 1 min,
|
||||||
|
para que la línea temporal de la demo siga siendo coherente (si no hay
|
||||||
|
ninguna hora legible, el reloj real). */
|
||||||
|
function horaTras() {
|
||||||
|
var metas = document.querySelectorAll("#chat-cuerpo .wa-row .meta");
|
||||||
|
var t = null;
|
||||||
|
for (var i = metas.length - 1; i >= 0 && !t; i--) {
|
||||||
|
t = String(metas[i].textContent || "").match(/(\d{1,2}):(\d{2})/);
|
||||||
|
}
|
||||||
|
if (!t) return WAM.horaAhora();
|
||||||
|
var min = (Number(t[1]) * 60 + Number(t[2]) + 1) % 1440;
|
||||||
|
return ("0" + Math.floor(min / 60)).slice(-2) + ":" + ("0" + (min % 60)).slice(-2);
|
||||||
|
}
|
||||||
|
|
||||||
/* ————————————————— temporizadores cancelables por grupo ————————————————— */
|
/* ————————————————— temporizadores cancelables por grupo ————————————————— */
|
||||||
|
|
||||||
@@ -215,12 +250,12 @@ window.WAM = window.WAM || {};
|
|||||||
return pintar(msg);
|
return pintar(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mensajeOut(texto, cita) {
|
function mensajeOut(texto, cita, hora) {
|
||||||
var msg = {
|
var msg = {
|
||||||
id: idSesion(),
|
id: idSesion(),
|
||||||
dir: "out",
|
dir: "out",
|
||||||
tipo: "text",
|
tipo: "text",
|
||||||
hora: WAM.horaAhora(),
|
hora: hora || WAM.horaAhora(),
|
||||||
estado: "sent",
|
estado: "sent",
|
||||||
texto: String(texto == null ? "" : texto)
|
texto: String(texto == null ? "" : texto)
|
||||||
};
|
};
|
||||||
@@ -243,7 +278,7 @@ window.WAM = window.WAM || {};
|
|||||||
if (TIPOS_VALIDOS.indexOf(copia.tipo) === -1) copia.tipo = "text";
|
if (TIPOS_VALIDOS.indexOf(copia.tipo) === -1) copia.tipo = "text";
|
||||||
copia.id = idSesion();
|
copia.id = idSesion();
|
||||||
copia.dir = "in";
|
copia.dir = "in";
|
||||||
copia.hora = WAM.horaAhora();
|
copia.hora = horaTras();
|
||||||
copia.estado = "sent";
|
copia.estado = "sent";
|
||||||
delete copia.cita;
|
delete copia.cita;
|
||||||
return copia;
|
return copia;
|
||||||
@@ -413,7 +448,7 @@ window.WAM = window.WAM || {};
|
|||||||
WAM.flujo = function (texto, respuestas, cita) {
|
WAM.flujo = function (texto, respuestas, cita) {
|
||||||
var lista = Array.isArray(respuestas) ? respuestas : [];
|
var lista = Array.isArray(respuestas) ? respuestas : [];
|
||||||
var ctx = contexto("flujo");
|
var ctx = contexto("flujo");
|
||||||
var out = mensajeOut(texto, cita || null);
|
var out = mensajeOut(texto, cita || null, horaTras());
|
||||||
anadirSesion(out);
|
anadirSesion(out);
|
||||||
var tics = animarTics(out, ctx);
|
var tics = animarTics(out, ctx);
|
||||||
tics.catch(nada);
|
tics.catch(nada);
|
||||||
@@ -460,7 +495,8 @@ window.WAM = window.WAM || {};
|
|||||||
if (ds.btn !== undefined) {
|
if (ds.btn !== undefined) {
|
||||||
var n = parseInt(ds.btn, 10);
|
var n = parseInt(ds.btn, 10);
|
||||||
var boton = (msg.botones || [])[n];
|
var boton = (msg.botones || [])[n];
|
||||||
if (!boton) return vacioR;
|
/* botón de enlace con URL bloqueada: inerte, nunca un quick reply */
|
||||||
|
if (!boton || boton.urlBloqueada) return vacioR;
|
||||||
return {
|
return {
|
||||||
texto: vacio(boton.texto) ? "Botón " + (n + 1) : String(boton.texto),
|
texto: vacio(boton.texto) ? "Botón " + (n + 1) : String(boton.texto),
|
||||||
respuestas: boton.respuestas || [],
|
respuestas: boton.respuestas || [],
|
||||||
@@ -472,7 +508,7 @@ window.WAM = window.WAM || {};
|
|||||||
if (ds.card !== undefined) {
|
if (ds.card !== undefined) {
|
||||||
var c = parseInt(ds.card, 10);
|
var c = parseInt(ds.card, 10);
|
||||||
var card = (msg.cards || [])[c];
|
var card = (msg.cards || [])[c];
|
||||||
if (!card) return vacioR;
|
if (!card || card.urlBloqueada) return vacioR;
|
||||||
return {
|
return {
|
||||||
texto: vacio(card.textoBoton) ? "Ver más" : String(card.textoBoton),
|
texto: vacio(card.textoBoton) ? "Ver más" : String(card.textoBoton),
|
||||||
respuestas: card.respuestas || [],
|
respuestas: card.respuestas || [],
|
||||||
@@ -509,11 +545,13 @@ window.WAM = window.WAM || {};
|
|||||||
return copia;
|
return copia;
|
||||||
});
|
});
|
||||||
var n = nuevos.length;
|
var n = nuevos.length;
|
||||||
|
if (typeof WAM.puntoDeshacer === "function") { try { WAM.puntoDeshacer(); } catch (e) { /* opcional */ } }
|
||||||
WAM.estado = WAM.normalizar ? WAM.normalizar({
|
WAM.estado = WAM.normalizar ? WAM.normalizar({
|
||||||
version: WAM.estado.version, tema: WAM.estado.tema, cuenta: WAM.estado.cuenta,
|
version: WAM.estado.version, tema: WAM.estado.tema, cuenta: WAM.estado.cuenta,
|
||||||
mensajes: WAM.estado.mensajes.concat(nuevos)
|
mensajes: WAM.estado.mensajes.concat(nuevos)
|
||||||
}) : WAM.estado;
|
}) : WAM.estado;
|
||||||
if (typeof WAM.guardar === "function") WAM.guardar();
|
if (typeof WAM.guardar === "function") WAM.guardar();
|
||||||
|
if (typeof WAM.marcarEditado === "function") { try { WAM.marcarEditado(); } catch (e) { /* opcional */ } }
|
||||||
if (typeof WAM.render === "function") WAM.render();
|
if (typeof WAM.render === "function") WAM.render();
|
||||||
if (typeof WAM.renderEditor === "function") WAM.renderEditor();
|
if (typeof WAM.renderEditor === "function") WAM.renderEditor();
|
||||||
return n;
|
return n;
|
||||||
@@ -594,7 +632,7 @@ window.WAM = window.WAM || {};
|
|||||||
|
|
||||||
async function flujoEnDemo(sel, ctx) {
|
async function flujoEnDemo(sel, ctx) {
|
||||||
if (!sel || !sel.msg) return;
|
if (!sel || !sel.msg) return;
|
||||||
var out = mensajeOut(sel.texto, sel.cita);
|
var out = mensajeOut(sel.texto, sel.cita, horaTras());
|
||||||
anadirSesion(out);
|
anadirSesion(out);
|
||||||
await animarTics(out, ctx);
|
await animarTics(out, ctx);
|
||||||
await responder(Array.isArray(sel.respuestas) ? sel.respuestas : [], ctx, true);
|
await responder(Array.isArray(sel.respuestas) ? sel.respuestas : [], ctx, true);
|
||||||
@@ -609,6 +647,12 @@ window.WAM = window.WAM || {};
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Progreso de la demo: mensajes del guion revelados / total. */
|
||||||
|
var progreso = { paso: 0, total: 0 };
|
||||||
|
WAM.progresoDemo = function () {
|
||||||
|
return demoAndando ? { paso: progreso.paso, total: progreso.total } : { paso: 0, total: 0 };
|
||||||
|
};
|
||||||
|
|
||||||
WAM.reproducirDemo = function () {
|
WAM.reproducirDemo = function () {
|
||||||
if (demoAndando) return;
|
if (demoAndando) return;
|
||||||
/* aborta flujos, ticks y typing en curso: no se mezclan con la demo */
|
/* aborta flujos, ticks y typing en curso: no se mezclan con la demo */
|
||||||
@@ -621,6 +665,8 @@ window.WAM = window.WAM || {};
|
|||||||
vaciarChat();
|
vaciarChat();
|
||||||
var ctx = contexto("demo");
|
var ctx = contexto("demo");
|
||||||
var secuencia = ((WAM.estado && WAM.estado.mensajes) || []).slice();
|
var secuencia = ((WAM.estado && WAM.estado.mensajes) || []).slice();
|
||||||
|
progreso.paso = 0;
|
||||||
|
progreso.total = secuencia.length;
|
||||||
(async function () {
|
(async function () {
|
||||||
try {
|
try {
|
||||||
for (var i = 0; i < secuencia.length; i++) {
|
for (var i = 0; i < secuencia.length; i++) {
|
||||||
@@ -629,6 +675,8 @@ window.WAM = window.WAM || {};
|
|||||||
if (msg.dir === "in") await escribirDurante(ctx, 700);
|
if (msg.dir === "in") await escribirDurante(ctx, 700);
|
||||||
if (!ctx.vivo()) throw CANCELADO;
|
if (!ctx.vivo()) throw CANCELADO;
|
||||||
pintar(msg);
|
pintar(msg);
|
||||||
|
progreso.paso = i + 1;
|
||||||
|
avisarDemo();
|
||||||
await ctx.esperar(600);
|
await ctx.esperar(600);
|
||||||
await autoPulsar(msg, ctx);
|
await autoPulsar(msg, ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-2
@@ -74,15 +74,55 @@ window.WAM = window.WAM || {};
|
|||||||
|
|
||||||
/* ————————————————— botón "Reproducir demo" ————————————————— */
|
/* ————————————————— botón "Reproducir demo" ————————————————— */
|
||||||
|
|
||||||
|
var SVG_STOP = '<rect x="6.5" y="6.5" width="11" height="11" rx="2"/>';
|
||||||
|
|
||||||
|
/* Contador discreto «Paso 3/12» junto al botón (se crea una vez). */
|
||||||
|
function contadorDemo(btn) {
|
||||||
|
var c = porId("demo-paso");
|
||||||
|
if (c) return c;
|
||||||
|
c = document.createElement("span");
|
||||||
|
c.id = "demo-paso";
|
||||||
|
c.className = "demo-paso";
|
||||||
|
c.setAttribute("aria-live", "polite");
|
||||||
|
c.setAttribute("role", "status");
|
||||||
|
c.hidden = true;
|
||||||
|
/* estilo mínimo en línea (tabular) mientras no haya regla en iphone.css */
|
||||||
|
c.style.fontSize = "12px";
|
||||||
|
c.style.fontVariantNumeric = "tabular-nums";
|
||||||
|
c.style.opacity = "0.75";
|
||||||
|
c.style.whiteSpace = "nowrap";
|
||||||
|
btn.parentNode.insertBefore(c, btn);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
function pintarBotonDemo() {
|
function pintarBotonDemo() {
|
||||||
var btn = porId("btn-reproducir");
|
var btn = porId("btn-reproducir");
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
var activa = WAM.demoActiva();
|
var activa = WAM.demoActiva();
|
||||||
btn.setAttribute("aria-pressed", activa ? "true" : "false");
|
btn.setAttribute("aria-pressed", activa ? "true" : "false");
|
||||||
|
var svg = btn.querySelector("svg");
|
||||||
|
if (svg) {
|
||||||
|
if (!svg.dataset.play) svg.dataset.play = svg.innerHTML;
|
||||||
|
var deseado = activa ? SVG_STOP : svg.dataset.play;
|
||||||
|
if (svg.innerHTML !== deseado) svg.innerHTML = deseado;
|
||||||
|
}
|
||||||
var span = btn.querySelector("span");
|
var span = btn.querySelector("span");
|
||||||
if (!span) return;
|
if (span) {
|
||||||
if (!span.dataset.etiqueta) span.dataset.etiqueta = span.textContent;
|
if (!span.dataset.etiqueta) span.dataset.etiqueta = span.textContent;
|
||||||
span.textContent = activa ? "■ Detener" : span.dataset.etiqueta;
|
span.textContent = activa ? "Detener" : span.dataset.etiqueta;
|
||||||
|
}
|
||||||
|
if (btn.parentNode) {
|
||||||
|
var c = contadorDemo(btn);
|
||||||
|
var p = typeof WAM.progresoDemo === "function" ? WAM.progresoDemo() : null;
|
||||||
|
if (activa && p && p.total > 0 && p.paso > 0) {
|
||||||
|
var txt = "Paso " + p.paso + "/" + p.total;
|
||||||
|
if (c.textContent !== txt) c.textContent = txt;
|
||||||
|
c.hidden = false;
|
||||||
|
} else {
|
||||||
|
c.hidden = true;
|
||||||
|
c.textContent = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Evita que un doble clic arranque y detenga la demo en el mismo gesto. */
|
/* Evita que un doble clic arranque y detenga la demo en el mismo gesto. */
|
||||||
|
|||||||
+74
-12
@@ -221,10 +221,54 @@ window.WAM = window.WAM || {};
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function lista(v, max) {
|
/* Contadores de lo que recorta la normalización en curso (ver
|
||||||
return (Array.isArray(v) ? v : []).slice(0, max || MAX_ITEMS);
|
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.
|
/* Rellena los campos que faltan por tipo y devuelve un estado válido.
|
||||||
Usado por cargar(), resetear() y editor.importarJSON (import / upgrade).
|
Usado por cargar(), resetear() y editor.importarJSON (import / upgrade).
|
||||||
- Acepta también un array de mensajes suelto como raíz.
|
- Acepta también un array de mensajes suelto como raíz.
|
||||||
@@ -239,6 +283,11 @@ window.WAM = window.WAM || {};
|
|||||||
: ((o && typeof o === "object") ? o : {});
|
: ((o && typeof o === "object") ? o : {});
|
||||||
|
|
||||||
entregados = new Set();
|
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();
|
var conjunto = new Set();
|
||||||
recolectarIds(src, conjunto);
|
recolectarIds(src, conjunto);
|
||||||
var tomar = crearAlocador(conjunto);
|
var tomar = crearAlocador(conjunto);
|
||||||
@@ -270,16 +319,19 @@ window.WAM = window.WAM || {};
|
|||||||
};
|
};
|
||||||
|
|
||||||
function respuestas(valor) {
|
function respuestas(valor) {
|
||||||
if (profundidad >= MAX_PROFUNDIDAD) return [];
|
if (profundidad >= MAX_PROFUNDIDAD) {
|
||||||
return lista(valor, 20).map(function (r) { return normalizarMensaje(r, true, profundidad + 1); });
|
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) {
|
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 boton = (b && typeof b === "object") ? b : { texto: typeof b === "string" ? b : "" };
|
||||||
var out = { texto: cad(boton.texto), respuestas: respuestas(boton.respuestas) };
|
var out = { texto: cad(boton.texto), respuestas: respuestas(boton.respuestas) };
|
||||||
/* Botón URL de plantilla (research-api §7.2): se conserva si viene */
|
/* 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;
|
return out;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -338,11 +390,11 @@ window.WAM = window.WAM || {};
|
|||||||
msg.body = cad(m.body);
|
msg.body = cad(m.body);
|
||||||
msg.footer = cad(m.footer);
|
msg.footer = cad(m.footer);
|
||||||
msg.boton = cad(m.boton);
|
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 : {};
|
var seccion = (s && typeof s === "object") ? s : {};
|
||||||
return {
|
return {
|
||||||
titulo: cad(seccion.titulo),
|
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 : "" };
|
var fila = (f && typeof f === "object") ? f : { titulo: typeof f === "string" ? f : "" };
|
||||||
return {
|
return {
|
||||||
titulo: cad(fila.titulo),
|
titulo: cad(fila.titulo),
|
||||||
@@ -363,15 +415,17 @@ window.WAM = window.WAM || {};
|
|||||||
break;
|
break;
|
||||||
case "carousel":
|
case "carousel":
|
||||||
msg.body = cad(m.body);
|
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 : {};
|
var card = (c && typeof c === "object") ? c : {};
|
||||||
return {
|
var salida = {
|
||||||
media: urlLimpia(card.media, "media"),
|
media: urlLimpia(card.media, "media"),
|
||||||
texto: cad(card.texto),
|
texto: cad(card.texto),
|
||||||
textoBoton: cad(card.textoBoton),
|
textoBoton: cad(card.textoBoton),
|
||||||
url: urlLimpia(card.url, "enlace"),
|
url: "",
|
||||||
respuestas: respuestas(card.respuestas)
|
respuestas: respuestas(card.respuestas)
|
||||||
};
|
};
|
||||||
|
urlEnlace(salida, card);
|
||||||
|
return salida;
|
||||||
});
|
});
|
||||||
autoPulsar();
|
autoPulsar();
|
||||||
break;
|
break;
|
||||||
@@ -389,7 +443,7 @@ window.WAM = window.WAM || {};
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cuentaSrc = (src.cuenta && typeof src.cuenta === "object") ? src.cuenta : {};
|
var cuentaSrc = (src.cuenta && typeof src.cuenta === "object") ? src.cuenta : {};
|
||||||
return {
|
var resultado = {
|
||||||
version: 1,
|
version: 1,
|
||||||
tema: src.tema === "oscuro" ? "oscuro" : "claro",
|
tema: src.tema === "oscuro" ? "oscuro" : "claro",
|
||||||
cuenta: {
|
cuenta: {
|
||||||
@@ -404,6 +458,14 @@ window.WAM = window.WAM || {};
|
|||||||
return normalizarMensaje(x, false, 0);
|
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 ————————————————— */
|
/* ————————————————— persistencia ————————————————— */
|
||||||
|
|||||||
Reference in New Issue
Block a user