Aplicar pendientes tras la integración

- render.js: botones y cards con urlBloqueada se pintan inertes.
- interactivity.js: sinFormato con la misma regla de marcadores que render.js; cita de documento con icono y nombre.
- Zoom manual (Ajustar / 100 % / 125 %) en #barra-movil, persistido en localStorage.
- Contador «Paso N/M» con estilo en iphone.css en lugar de estilos en línea.
- store/editor: campos opcionales tamano y headerTamano; editar la URL retira urlBloqueada;
  urlValida acepta URLs con forma de dominio; importar avisa de lo recortado.
- README y SPEC actualizados.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
urieljareth
2026-09-27 21:39:09 -06:00
co-authored by Claude Opus 5.5
parent a645056c00
commit 4fe8f73959
11 changed files with 136 additions and 28 deletions
+18 -3
View File
@@ -450,7 +450,10 @@
if (obj[k] === null || obj[k] === undefined) obj[k] = /^\d+$/.test(partes[i + 1]) ? [] : {};
obj = obj[k];
}
obj[partes[partes.length - 1]] = valor;
var clave = partes[partes.length - 1];
obj[clave] = valor;
// Editar la URL a mano retira la marca de enlace bloqueado (store.js)
if (clave === "url" && obj.urlBloqueada) delete obj.urlBloqueada;
if (typeof WAM.guardar === "function") WAM.guardar();
marcarEditado();
if (opts && opts.diferido) { programarRender(String(ruta)); return; }
@@ -978,6 +981,8 @@
} finally {
WAM.estado = previo;
}
// Se lee ya: cualquier normalización posterior sobrescribe el resumen
var recortes = typeof WAM.describirRecortes === "function" && datos ? WAM.describirRecortes(datos.__recortes) : "";
if (!datos || !Array.isArray(datos.mensajes)) throw new Error("sin mensajes");
snap();
WAM.estado = datos;
@@ -992,7 +997,7 @@
if (typeof WAM.render === "function") WAM.render();
WAM.renderEditor();
var n = WAM.estado.mensajes.length;
WAM.toast("Demo importada: " + n + " mensaje" + (n === 1 ? "" : "s"),
WAM.toast(recortes ? recortes + "." : "Demo importada: " + n + " mensaje" + (n === 1 ? "" : "s"),
typeof WAM.deshacer === "function" ? { accion: { texto: "Deshacer", fn: WAM.deshacer }, ms: 8000 } : null);
var primero = listaNodo() && listaNodo().querySelector(".ed-cab-principal");
if (primero) primero.focus();
@@ -1038,7 +1043,13 @@
------------------------------------------------------------------ */
function largo(v) { return String(v === undefined || v === null ? "" : v).length; }
function vacioTxt(v) { return !String(v === undefined || v === null ? "" : v).trim(); }
function urlValida(v) { return /^https?:\/\/[^\s/$.?#][^\s]*$/i.test(String(v || "").trim()); }
// Con esquema http(s) o con forma de dominio («www.cafe.com», «cafe.com/menu»):
// render.js abre estas últimas con https://, igual que WhatsApp.
function urlValida(v) {
var s = String(v || "").trim();
return /^https?:\/\/[^\s/$.?#][^\s]*$/i.test(s) ||
/^www\.[^\s]+$/i.test(s) || /^[\w-]+(\.[\w-]+)+(:\d+)?([\/?#][^\s]*)?$/.test(s);
}
// Botón de llamada de plantilla: tel: con al menos 3 dígitos (espacios, guiones y paréntesis permitidos)
function telValido(v) {
var s = String(v || "").trim();
@@ -1716,6 +1727,9 @@
caja.appendChild(edCampo("Nombre del documento (con extensión; define el icono)", ruta + ".headerTexto", m.headerTexto, {
max: 80, contar: true, placeholder: "catalogo.pdf", spell: false
}));
caja.appendChild(edCampo("Tamaño (opcional, p. ej. 243 KB)", ruta + ".headerTamano", m.headerTamano, {
max: 20, placeholder: "243 KB", spell: false
}));
}
caja.appendChild(edMedia("Media de cabecera", ruta + ".headerMedia", m.headerMedia, {
aceptar: tipo === "imagen" ? "image/*" : (tipo === "video" ? "video/*" : ""),
@@ -1801,6 +1815,7 @@
break;
case "document":
ap(edCampo("Nombre de archivo (con extensión; define el icono)", ruta + ".filename", m.filename, { max: 80, contar: true, placeholder: "menu.pdf" }));
ap(edCampo("Tamaño (opcional, p. ej. 243 KB)", ruta + ".tamano", m.tamano, { max: 20, placeholder: "243 KB", spell: false }));
ap(edTextarea("Caption (opcional)", ruta + ".caption", m.caption, { limite: 1024 }));
ap(edMedia("Archivo (opcional, no se muestra)", ruta + ".src", m.src, { tipoMedia: "doc", textoDrop: "Clic o arrastra un documento aquí" }));
break;
+26 -4
View File
@@ -292,12 +292,23 @@ window.WAM = window.WAM || {};
return String(s == null ? "" : s)
.replace(/```([\s\S]*?)```/g, "$1")
.replace(/`([^`\n]+)`/g, "$1")
.replace(/\*([^*\n]+)\*/g, "$1")
.replace(/(^|[^\w])_([^_\n]+)_(?=[^\w]|$)/g, "$1$2")
.replace(/(^|[^\w])~([^~\n]+)~(?=[^\w]|$)/g, "$1$2")
.replace(reMarca("\*"), "$1$2")
.replace(reMarca("_"), "$1$2")
.replace(reMarca("~"), "$1$2")
.trim();
}
/* Misma regla que render.js: marca pegada a un carácter no blanco por dentro
y sin letra, número ni _ por fuera («2 * 3 * 4» queda literal). */
function reMarca(e) {
try {
return new RegExp("(^|[^\p{L}\p{N}_" + e + "])" + e + "(?=\S)([^" + e + "\n]*?\S)" + e +
"(?=[^\p{L}\p{N}_" + e + "]|$)", "gu");
} catch (err) {
return new RegExp("(^|[^\w" + e + "])" + e + "(?=\S)([^" + e + "\n]*?\S)" + e + "(?=[^\w" + e + "]|$)", "g");
}
}
function miniaturaSegura(u) {
if (vacio(u)) return "";
var s = String(u);
@@ -340,6 +351,9 @@ window.WAM = window.WAM || {};
cita.foto = true;
} else if (msg.headerTipo === "video") {
cita.foto = true;
} else if (msg.headerTipo === "documento") {
cita.titulo = vacio(msg.headerTexto) ? "documento.pdf" : String(msg.headerTexto);
cita.doc = true;
}
}
cita.extracto = partes.filter(function (p) { return !vacio(p); }).map(sinFormato).join("\n");
@@ -350,6 +364,9 @@ window.WAM = window.WAM || {};
var SVG_CAMARA = '<svg class="wa-cita-cam" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">' +
'<path fill="currentColor" d="M5.6 2.5h4.8l1 1.5H13a1.5 1.5 0 0 1 1.5 1.5v6A1.5 1.5 0 0 1 13 12.5H3A1.5 1.5 0 0 1 1.5 11V5.5A1.5 1.5 0 0 1 3 4h1.6l1-1.5Zm2.4 3a2.75 2.75 0 1 0 0 5.5 2.75 2.75 0 0 0 0-5.5Z"/></svg>';
var SVG_DOC = '<svg class="wa-cita-doc" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">' +
'<path fill="currentColor" d="M4 1.5h5.3L13 5.2V13a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 3 13V3a1.5 1.5 0 0 1 1-1.5Zm5 .9V5.5h3.1L9 2.4ZM5 8h6v1H5V8Zm0 2.2h6v1H5v-1Z"/></svg>';
/* Bloque de cita dentro de la burbuja OUT; todo por textContent (sin XSS). */
function pintarCita(fila, cita) {
var burbuja = fila.querySelector(".bubble");
@@ -365,7 +382,12 @@ window.WAM = window.WAM || {};
if (cita.titulo) {
var tit = document.createElement("div");
tit.className = "wa-cita-titulo";
tit.textContent = cita.titulo;
if (cita.doc) {
var doc = document.createElement("span");
doc.innerHTML = SVG_DOC; /* constante propia, sin datos del usuario */
if (doc.firstChild) tit.appendChild(doc.firstChild);
}
tit.appendChild(document.createTextNode(cita.titulo));
txt.appendChild(tit);
}
if (cita.extracto) {
-5
View File
@@ -86,11 +86,6 @@ window.WAM = window.WAM || {};
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;
}
+2 -2
View File
@@ -427,7 +427,7 @@
return '<a class="qr-btn"' + h + ' href="' + esc(url) + '" target="_blank" rel="noopener noreferrer" data-btn-url="' + j + '">' +
icono(tipoIconoUrl(url)) + lbl + "</a>";
}
if (!vacio(b.url)) {
if (!vacio(b.url) || b.urlBloqueada) {
return '<span class="qr-btn qr-btn-inactiva"' + h + ' aria-disabled="true">' + icono("url") + lbl + "</span>";
}
return '<button type="button" class="qr-btn"' + h + ' data-flujo="' + i + '" data-btn="' + j + '">' +
@@ -540,7 +540,7 @@
var url = urlSegura(c.url);
var boton;
if (url) boton = '<a class="qr-btn" href="' + esc(url) + '" target="_blank" rel="noopener noreferrer">' + icono(tipoIconoUrl(url)) + lbl + "</a>";
else if (!vacio(c.url)) boton = '<span class="qr-btn qr-btn-inactiva" aria-disabled="true">' + icono("url") + lbl + "</span>";
else if (!vacio(c.url) || c.urlBloqueada) boton = '<span class="qr-btn qr-btn-inactiva" aria-disabled="true">' + icono("url") + lbl + "</span>";
else boton = '<button type="button" class="qr-btn" data-flujo="' + i + '" data-card="' + j + '">' + icono("responder") + lbl + "</button>";
html += '<div class="card">' +
'<img class="card-media" src="' + esc(media) + '" alt="" draggable="false">' +
+5
View File
@@ -359,6 +359,8 @@ window.WAM = window.WAM || {};
msg.src = urlLimpia(m.src, "media");
msg.caption = cad(m.caption);
msg.filename = cad(m.filename, "documento.pdf") || "documento.pdf";
/* opcional: «243 KB» para el subtítulo «243 KB • pdf» (render.js) */
if (cad(m.tamano).trim()) msg.tamano = cad(m.tamano).trim().slice(0, 20);
break;
case "sticker":
msg.src = urlLimpia(m.src, "media");
@@ -380,6 +382,9 @@ window.WAM = window.WAM || {};
msg.headerTipo = HEADER_TIPOS.indexOf(m.headerTipo) !== -1 ? m.headerTipo : "";
msg.headerTexto = cad(m.headerTexto);
msg.headerMedia = urlLimpia(m.headerMedia, "media");
if (msg.headerTipo === "documento" && cad(m.headerTamano).trim()) {
msg.headerTamano = cad(m.headerTamano).trim().slice(0, 20);
}
msg.body = cad(m.body);
msg.footer = cad(m.footer);
msg.botones = botones(m.botones);
+25
View File
@@ -955,6 +955,30 @@
temaPintado = tema;
}
/* ------------------------------------------------------------------
Zoom manual del teléfono (Ajustar / 100 % / 125 %). El CSS vive en
iphone.css (body[data-wam-zoom]); sin atributo = ajustar al hueco.
------------------------------------------------------------------ */
var CLAVE_ZOOM = "wam-zoom";
function aplicarZoom(v) {
v = v === "100" || v === "125" ? v : "";
if (v) document.body.setAttribute("data-wam-zoom", v);
else document.body.removeAttribute("data-wam-zoom");
var sel = porId("sel-zoom");
if (sel && sel.value !== v) sel.value = v;
return v;
}
WAM.setZoom = function (v) {
v = aplicarZoom(String(v == null ? "" : v));
if (v) lsSet(CLAVE_ZOOM, v); else lsDel(CLAVE_ZOOM);
return v;
};
function enlazarZoom() {
aplicarZoom(lsGet(CLAVE_ZOOM) || "");
var sel = porId("sel-zoom");
if (sel) sel.addEventListener("change", function () { WAM.setZoom(sel.value); });
}
/* ------------------------------------------------------------------
Inicialización (una sola vez)
------------------------------------------------------------------ */
@@ -978,6 +1002,7 @@
observarToolbar();
enlazarChat();
enlazarSaltos();
enlazarZoom();
envolverAnuncios();
sincronizarTema();