Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d208a2d935 |
@@ -17,7 +17,6 @@ scripts/ — scripts Python/PHP de migración, traducción, TTS y mantenimi
|
||||
| `fea-homepage.php` | Tarjetas de portada |
|
||||
| `fea-share.php` | Sección "Comparte FeAdulta" (Facebook, Instagram, Imprimir) |
|
||||
| `fea-ui.php` | Estilos globales, menú, fondo cálido en artículos |
|
||||
| `fea-accesibilidad.php` | Tipografía Atkinson Hyperlegible (self-hosted) + toggle claro/oscuro persistente |
|
||||
| `fea-beta-feedback.php` | Barra de feedback beta + endpoint `/fea/v1/lang/{id}` |
|
||||
| `fea-search.php` | Buscador móvil nativo |
|
||||
| `fea-pensamientos.php` | Widget de pensamientos |
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Fe Adulta — Accesibilidad (tipografía + contraste)
|
||||
* Description: Tipografía Atkinson Hyperlegible global (self-hosted) + toggle
|
||||
* claro/oscuro persistente, pensado para el público mayor de
|
||||
* Fe Adulta (issue rafa/feadulta#196, evidencia en comment #609).
|
||||
* Version: 1.0
|
||||
*
|
||||
* Alcance decidido en el issue (2026-08-08): SOLO dos cosas, sin más superficie
|
||||
* de UI que la necesaria —
|
||||
* 1. Atkinson Hyperlegible sustituye la tipografía del cuerpo, sin selector.
|
||||
* 2. Un único toggle claro/oscuro persistente (claro por defecto).
|
||||
* Nada de panel de accesibilidad, A+/Aa ni cambio de tamaño de letra.
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
if (!defined('FEA_A11Y_ACCENT_DARK')) {
|
||||
// Variante clara del carmesí de marca (#8b1a2e) para que enlaces/foco
|
||||
// cumplan contraste AA sobre fondo casi negro (el carmesí original da
|
||||
// ~1.8:1 sobre #0d0d0d, muy por debajo del 4.5:1 que exige texto).
|
||||
define('FEA_A11Y_ACCENT_DARK', '#e8899e');
|
||||
}
|
||||
|
||||
/* ── 1) Anti-flash: fija el tema ANTES de que el navegador pinte nada ─────
|
||||
* Se engancha con prioridad muy baja para salir de los primeros bytes de
|
||||
* <head>, igual que el resto del sitio guarda preferencias en localStorage
|
||||
* (ver fea-beta-feedback.php). Sin esto habría un parpadeo claro→oscuro. */
|
||||
add_action('wp_head', function () {
|
||||
if (is_admin()) return;
|
||||
?>
|
||||
<script>(function(){try{
|
||||
var t = localStorage.getItem('fea_theme');
|
||||
if (t === 'dark') document.documentElement.setAttribute('data-fea-theme', 'dark');
|
||||
}catch(e){}})();</script>
|
||||
<?php
|
||||
}, 1);
|
||||
|
||||
/* ── 2) Fuente self-hosted + estilos de tema oscuro ───────────────────── */
|
||||
add_action('wp_head', function () {
|
||||
if (is_admin()) return;
|
||||
$base = plugins_url('fea-accesibilidad/fonts', __FILE__);
|
||||
$accent_dark = FEA_A11Y_ACCENT_DARK;
|
||||
?>
|
||||
<style id="fea-a11y">
|
||||
/* ── Atkinson Hyperlegible (Braille Institute, OFL), subset latin ──── */
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible';
|
||||
font-style: normal;
|
||||
font-weight: 100 500;
|
||||
font-display: swap;
|
||||
src: url('<?php echo esc_url($base); ?>/atkinson-hyperlegible-regular.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible';
|
||||
font-style: normal;
|
||||
font-weight: 501 900;
|
||||
font-display: swap;
|
||||
src: url('<?php echo esc_url($base); ?>/atkinson-hyperlegible-bold.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* Sustitución global del cuerpo de texto (no opcional, issue #196).
|
||||
Especificidad baja a propósito: el menú (fea-ui.php, issue #95) usa
|
||||
una sans de UI más específica y debe seguir ganando. */
|
||||
body {
|
||||
font-family: 'Atkinson Hyperlegible', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
.fea-hero-title,
|
||||
.fea-section-title {
|
||||
font-family: 'Atkinson Hyperlegible', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
input, textarea, select, button {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* ── Toggle claro/oscuro: botón inyectado junto al selector de idioma ── */
|
||||
#fea-theme-btn {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 30px; height: 30px; border-radius: 50%;
|
||||
border: 1px solid rgba(0,0,0,0.25); background: none; color: inherit;
|
||||
cursor: pointer; font-size: 0.95rem; line-height: 1; padding: 0;
|
||||
}
|
||||
#fea-theme-btn:hover { background: rgba(0,0,0,0.06); }
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════
|
||||
MODO OSCURO — activado por [data-fea-theme="dark"] en <html>
|
||||
══════════════════════════════════════════════════════════════════ */
|
||||
html[data-fea-theme="dark"] {
|
||||
/* Deja que el navegador oscurezca controles nativos gratis:
|
||||
audio/video, scrollbars, checkboxes, selects sin estilo propio. */
|
||||
color-scheme: dark;
|
||||
--wp--preset--color--base: #0d0d0d;
|
||||
--wp--preset--color--contrast: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] body {
|
||||
background-color: #0d0d0d !important;
|
||||
color: #ececec !important;
|
||||
}
|
||||
html[data-fea-theme="dark"] a { color: <?php echo esc_html($accent_dark); ?>; }
|
||||
html[data-fea-theme="dark"] a:focus-visible,
|
||||
html[data-fea-theme="dark"] button:focus-visible {
|
||||
outline-color: <?php echo esc_html($accent_dark); ?> !important;
|
||||
}
|
||||
|
||||
/* Cabecera/pie y bloques de grupo sin fondo explícito heredan el body;
|
||||
solo hay que corregir los pocos sitios con color fijo en claro. */
|
||||
html[data-fea-theme="dark"] input,
|
||||
html[data-fea-theme="dark"] textarea,
|
||||
html[data-fea-theme="dark"] select {
|
||||
background-color: #1c1c1c;
|
||||
color: #ececec;
|
||||
border-color: #3a3a3a;
|
||||
}
|
||||
|
||||
/* Search block uses more-specific own color rules. */
|
||||
html[data-fea-theme="dark"] .wp-block-search__inside-wrapper,
|
||||
html[data-fea-theme="dark"] .wp-block-search__input {
|
||||
background-color: #1c1c1c;
|
||||
color: #ececec;
|
||||
border-color: #3a3a3a;
|
||||
}
|
||||
html[data-fea-theme="dark"] .wp-block-search__button {
|
||||
background-color: #ececec;
|
||||
color: #0d0d0d;
|
||||
border-color: #ececec;
|
||||
}
|
||||
|
||||
html[data-fea-theme="dark"] .fea-adv-link-wrap { background: #161311; border-bottom-color: #2a2320; }
|
||||
html[data-fea-theme="dark"] .fea-adv-link { color: <?php echo esc_html($accent_dark); ?>; }
|
||||
|
||||
/* Crop the white edge embedded in slide images in dark mode only. */
|
||||
html[data-fea-theme="dark"] .fea-hero-slider .n2-ss-slide-background-image {
|
||||
overflow: hidden;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-hero-slider .n2-ss-slide-background-image img {
|
||||
transform: scale(1.015);
|
||||
}
|
||||
|
||||
/* Portada — franja hero (fea-homepage.php) */
|
||||
html[data-fea-theme="dark"] .fea-hero-band {
|
||||
background: linear-gradient(180deg, #1a1512, #0d0d0d);
|
||||
border-bottom-color: #2a2320;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-hero-title,
|
||||
html[data-fea-theme="dark"] .fea-section-title {
|
||||
color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-section-label,
|
||||
html[data-fea-theme="dark"] .fea-section-more,
|
||||
html[data-fea-theme="dark"] .fea-card-author {
|
||||
color: <?php echo esc_html($accent_dark); ?>;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-hero-meta { color: #b8afa8; }
|
||||
|
||||
/* Tarjetas de autor/artículo (fea-homepage.php) */
|
||||
html[data-fea-theme="dark"] .fea-card {
|
||||
background: #161311;
|
||||
border-color: #2a2320;
|
||||
}
|
||||
html[data-fea-theme="dark"] .fea-card-title a { color: #ececec; }
|
||||
|
||||
/* Selector de idioma (fea-homepage.php) */
|
||||
html[data-fea-theme="dark"] #fea-lang-btn { border-color: rgba(255,255,255,0.3); }
|
||||
html[data-fea-theme="dark"] #fea-lang-btn:hover,
|
||||
html[data-fea-theme="dark"] #fea-theme-btn:hover { background: rgba(255,255,255,0.08); }
|
||||
html[data-fea-theme="dark"] #fea-theme-btn { border-color: rgba(255,255,255,0.3); }
|
||||
html[data-fea-theme="dark"] #fea-lang-dropdown {
|
||||
background: #161311; border-color: #3a3a3a;
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,.5);
|
||||
}
|
||||
html[data-fea-theme="dark"] #fea-lang-dropdown a { color: #ececec; }
|
||||
html[data-fea-theme="dark"] #fea-lang-dropdown a:hover { background: #241f1c; }
|
||||
html[data-fea-theme="dark"] #fea-lang-dropdown a[aria-current] { background: #2a2320; color: #fff; }
|
||||
|
||||
/* Buscador móvil (fea-search.php) */
|
||||
html[data-fea-theme="dark"] .fea-search-bar { background: #0d0d0d; border-bottom-color: #2a2320; }
|
||||
html[data-fea-theme="dark"] .fea-search { background: #1c1c1c; border-color: #3a3a3a; }
|
||||
html[data-fea-theme="dark"] .fea-search input[type=search] { color: #ececec; }
|
||||
|
||||
/* Reproductor de audio (fea-audio-player.php) — el <audio> nativo ya
|
||||
cambia solo vía color-scheme; solo hace falta la etiqueta de texto. */
|
||||
html[data-fea-theme="dark"] .fea-audio-label { color: #b8afa8; }
|
||||
|
||||
/* Barra y tarjeta de Beta Feedback (fea-beta-feedback.php) */
|
||||
html[data-fea-theme="dark"] #fea-beta-bar {
|
||||
background: #161311; border-top-color: #2a2320; color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] #fea-beta-bar .fea-beta-dismiss { color: #b8afa8; }
|
||||
html[data-fea-theme="dark"] #fea-fb .fea-fb-card {
|
||||
background: #161311; border-color: #3a3a3a; color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] #fea-fb button.fea-fb-vote {
|
||||
background: #1c1c1c; border-color: #3a3a3a; color: #ececec;
|
||||
}
|
||||
html[data-fea-theme="dark"] #fea-fb button.fea-fb-vote.sel {
|
||||
border-color: <?php echo esc_html($accent_dark); ?>; background: #2a1418;
|
||||
}
|
||||
html[data-fea-theme="dark"] #fea-fb textarea { background: #1c1c1c; border-color: #3a3a3a; color: #ececec; }
|
||||
|
||||
/* Barra de consentimiento de cookies (fea-cookie-consent.php) */
|
||||
html[data-fea-theme="dark"] #fea-cc {
|
||||
background: #161311 !important; color: #ececec !important; border-top-color: #2a2320 !important;
|
||||
}
|
||||
html[data-fea-theme="dark"] #fea-cc a { color: #b8afa8 !important; }
|
||||
html[data-fea-theme="dark"] #fea-cc .fea-cc-reject { color: #ececec !important; border-color: #3a3a3a !important; }
|
||||
</style>
|
||||
<?php
|
||||
}, 30);
|
||||
|
||||
/* ── 3) Botón de toggle, inyectado en el header junto al selector de idioma ──
|
||||
* Mismo patrón que el selector de idioma (fea-homepage.php): se crea por JS
|
||||
* y se inserta en el nav del header, para no tocar el template FSE. */
|
||||
add_action('wp_footer', function () {
|
||||
if (is_admin()) return;
|
||||
?>
|
||||
<script>
|
||||
(function() {
|
||||
var btn = document.createElement('button');
|
||||
btn.id = 'fea-theme-btn';
|
||||
btn.type = 'button';
|
||||
btn.setAttribute('aria-pressed', document.documentElement.getAttribute('data-fea-theme') === 'dark' ? 'true' : 'false');
|
||||
btn.setAttribute('aria-label', 'Cambiar a modo oscuro');
|
||||
btn.textContent = document.documentElement.getAttribute('data-fea-theme') === 'dark' ? '☀️' : '🌙';
|
||||
|
||||
function setLabel(isDark) {
|
||||
btn.setAttribute('aria-pressed', isDark ? 'true' : 'false');
|
||||
btn.setAttribute('aria-label', isDark ? 'Cambiar a modo claro' : 'Cambiar a modo oscuro');
|
||||
btn.textContent = isDark ? '☀️' : '🌙';
|
||||
}
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var isDark = document.documentElement.getAttribute('data-fea-theme') === 'dark';
|
||||
if (isDark) {
|
||||
document.documentElement.removeAttribute('data-fea-theme');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-fea-theme', 'dark');
|
||||
}
|
||||
setLabel(!isDark);
|
||||
try { localStorage.setItem('fea_theme', !isDark ? 'dark' : 'light'); } catch (e) {}
|
||||
});
|
||||
|
||||
function inject() {
|
||||
var header = document.querySelector('header.wp-block-template-part') || document.querySelector('header');
|
||||
if (!header) return;
|
||||
var navGroup = header.querySelector('.wp-block-navigation__container') || header.querySelector('nav');
|
||||
var li = document.createElement('li');
|
||||
li.className = 'wp-block-navigation-item';
|
||||
li.style.cssText = 'display:flex;align-items:center;margin-left:0.5rem;';
|
||||
li.appendChild(btn);
|
||||
if (!navGroup) {
|
||||
header.style.position = 'relative';
|
||||
li.style.cssText = 'position:absolute;top:50%;right:1.5rem;transform:translateY(-50%);';
|
||||
header.appendChild(li);
|
||||
return;
|
||||
}
|
||||
// Insertar después del selector de idioma si ya se inyectó, si no, al final del nav.
|
||||
var langSwitcher = navGroup.querySelector('#fea-lang-switcher');
|
||||
if (langSwitcher) {
|
||||
var anchor = langSwitcher;
|
||||
while (anchor.parentNode && anchor.parentNode !== navGroup) anchor = anchor.parentNode;
|
||||
anchor.parentNode.insertBefore(li, anchor.nextSibling);
|
||||
} else {
|
||||
navGroup.appendChild(li);
|
||||
}
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', inject);
|
||||
} else {
|
||||
inject();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
}, 21);
|
||||
Binary file not shown.
Binary file not shown.
@@ -13,23 +13,11 @@ function fea_audio_player_html(): string {
|
||||
$url = get_post_meta($id, 'fea_audio_url', true);
|
||||
if (!$url) return '';
|
||||
$voice = get_post_meta($id, 'fea_audio_voice', true) ?: 'NicoFeadulta2026';
|
||||
$sha256 = get_post_meta($id, 'fea_audio_sha256', true);
|
||||
$label = '<span class="fea-audio-label">'
|
||||
return '<div class="fea-audio">'
|
||||
. '<span class="fea-audio-label">'
|
||||
. '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false">'
|
||||
. '<path fill="currentColor" d="M3 10v4h4l5 5V5L7 10H3zm13.5 2a4.5 4.5 0 0 0-2.5-4.03v8.06A4.5 4.5 0 0 0 16.5 12zM14 3.23v2.06a7 7 0 0 1 0 13.42v2.06a9 9 0 0 0 0-17.54z"/>'
|
||||
. '</svg> Escucha</span>';
|
||||
// Para un asset con hash registrado, descarga y verifica el fichero completo
|
||||
// antes de delegar playback al navegador. Evita que un stream Range mezclado
|
||||
// por una caché reproduzca fragmentos de otro MP3.
|
||||
if (preg_match('/^[a-f0-9]{64}$/i', $sha256)) {
|
||||
return '<div class="fea-audio">' . $label
|
||||
. '<button type="button" class="fea-audio-verified-play" data-fea-audio-verified-play'
|
||||
. ' data-src="' . esc_url($url) . '" data-sha256="' . esc_attr(strtolower($sha256)) . '"'
|
||||
. ' data-post-id="' . esc_attr($id) . '" data-voice="' . esc_attr($voice) . '">Reproducir audio</button>'
|
||||
. '<audio controls preload="none" hidden data-fea-audio-track data-post-id="' . esc_attr($id) . '" data-voice="' . esc_attr($voice) . '"></audio>'
|
||||
. '<span class="fea-audio-status" role="status" aria-live="polite"></span></div>';
|
||||
}
|
||||
return '<div class="fea-audio">' . $label
|
||||
. '</svg> Escucha</span>'
|
||||
. '<audio controls preload="none" src="' . esc_url($url) . '"'
|
||||
. ' data-fea-audio-track data-post-id="' . esc_attr($id) . '" data-voice="' . esc_attr($voice) . '"></audio>'
|
||||
. '</div>';
|
||||
@@ -68,9 +56,6 @@ add_action('wp_head', function () {
|
||||
.fea-audio-label{display:inline-flex;align-items:center;gap:.35rem;font-size:.78rem;
|
||||
font-weight:600;color:#8b1a2e;white-space:nowrap;line-height:1}
|
||||
.fea-audio audio{height:32px;width:230px;max-width:44vw}
|
||||
.fea-audio-verified-play{border:1px solid #8b1a2e;border-radius:6px;background:#fff;color:#8b1a2e;padding:.35rem .6rem;font:inherit;font-size:.82rem;font-weight:600;cursor:pointer}
|
||||
.fea-audio-verified-play:disabled{opacity:.65;cursor:wait}
|
||||
.fea-audio-status{font-size:.76rem;color:#6b5c60}
|
||||
@media(max-width:600px){
|
||||
.fea-audio{margin-left:0;width:100%;margin-top:.5rem}
|
||||
.fea-audio audio{flex:1 1 auto;width:auto;max-width:none}
|
||||
@@ -88,40 +73,6 @@ add_action('wp_footer', function () {
|
||||
if (!get_post_meta(get_queried_object_id(), 'fea_audio_url', true)) return;
|
||||
?>
|
||||
<script>
|
||||
(function () {
|
||||
function hex(buffer) {
|
||||
return Array.from(new Uint8Array(buffer)).map(function (n) {
|
||||
return n.toString(16).padStart(2, '0');
|
||||
}).join('');
|
||||
}
|
||||
document.querySelectorAll('[data-fea-audio-verified-play]').forEach(function (button) {
|
||||
button.addEventListener('click', async function () {
|
||||
var wrap = button.closest('.fea-audio');
|
||||
var audio = wrap.querySelector('audio[data-fea-audio-track]');
|
||||
var status = wrap.querySelector('.fea-audio-status');
|
||||
button.disabled = true;
|
||||
status.textContent = 'Verificando audio…';
|
||||
try {
|
||||
// Se descarga el objeto entero una vez y se valida su hash. Sólo
|
||||
// entonces se crea un blob local: el <audio> no hará Range contra
|
||||
// CDN y jamás podrá mezclar fragmentos de otro MP3.
|
||||
var response = await fetch(button.dataset.src, {cache: 'no-store'});
|
||||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||
var bytes = await response.arrayBuffer();
|
||||
var digest = hex(await crypto.subtle.digest('SHA-256', bytes));
|
||||
if (digest !== button.dataset.sha256) throw new Error('La copia recibida no coincide con el audio verificado');
|
||||
audio.src = URL.createObjectURL(new Blob([bytes], {type: 'audio/mpeg'}));
|
||||
audio.hidden = false;
|
||||
button.hidden = true;
|
||||
status.textContent = '';
|
||||
await audio.play();
|
||||
} catch (error) {
|
||||
status.textContent = 'No se pudo verificar el audio. Inténtalo de nuevo.';
|
||||
button.disabled = false;
|
||||
console.error('Fe Adulta audio verification failed:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('audio[data-fea-audio-track]').forEach(function (audio) {
|
||||
var fired = false;
|
||||
audio.addEventListener('play', function () {
|
||||
@@ -133,7 +84,6 @@ add_action('wp_footer', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
<?php
|
||||
});
|
||||
|
||||
@@ -924,6 +924,58 @@ function fea_is_front_page(): bool {
|
||||
return $id && in_array($id, fea_front_page_ids(), true);
|
||||
}
|
||||
|
||||
/** URL de la búsqueda avanzada en el idioma actual, con fallback al español. */
|
||||
function fea_home_advanced_search_url(): string {
|
||||
$url = home_url('/buscar/');
|
||||
if (!function_exists('pll_current_language') || !function_exists('pll_get_post')) return $url;
|
||||
|
||||
$lang = pll_current_language();
|
||||
$default = function_exists('pll_default_language') ? pll_default_language() : 'es';
|
||||
if (!$lang || $lang === $default) return $url;
|
||||
|
||||
$page = get_page_by_path('buscar');
|
||||
if (!$page) return $url;
|
||||
|
||||
$translated_id = pll_get_post($page->ID, $lang);
|
||||
return $translated_id ? get_permalink($translated_id) : $url;
|
||||
}
|
||||
|
||||
// En desktop, una lupa sin texto abre la búsqueda avanzada en vez de activar la
|
||||
// validación nativa del campo requerido del bloque Search de WordPress.
|
||||
add_action('wp_footer', function() {
|
||||
if (!fea_is_front_page()) return;
|
||||
$advanced_url = wp_json_encode(fea_home_advanced_search_url());
|
||||
?>
|
||||
<script>
|
||||
(function() {
|
||||
function configureDesktopSearch() {
|
||||
if (!window.matchMedia('(min-width: 601px)').matches) return;
|
||||
|
||||
document.querySelectorAll('form.wp-block-search, .wp-block-search form').forEach(function(form) {
|
||||
var input = form.querySelector('input[type="search"], input[name="s"]');
|
||||
if (!input || form.dataset.feaEmptySearchReady) return;
|
||||
|
||||
form.dataset.feaEmptySearchReady = 'true';
|
||||
form.noValidate = true;
|
||||
input.removeAttribute('required');
|
||||
form.addEventListener('submit', function(event) {
|
||||
if (input.value.trim() !== '') return;
|
||||
event.preventDefault();
|
||||
window.location.assign(<?php echo $advanced_url; ?>);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', configureDesktopSearch);
|
||||
} else {
|
||||
configureDesktopSearch();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
});
|
||||
|
||||
/** True para la landing de Escuela, que no muestra título visible. */
|
||||
function fea_is_escuela_page(): bool {
|
||||
if (!is_page()) return false;
|
||||
|
||||
@@ -19,7 +19,6 @@ Rollback (despublica en prod lo que este script publicó):
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
@@ -145,21 +144,16 @@ def prod_helper(subcmd: str, *args: str) -> str:
|
||||
return _ssh_text(_remote_wrap(inner), timeout=60)
|
||||
|
||||
|
||||
def prod_upload_mp3(post_id: int) -> str:
|
||||
def prod_upload_mp3(post_id: int) -> None:
|
||||
src = LOCAL_TTS_DIR / f"{post_id}.mp3"
|
||||
data = src.read_bytes()
|
||||
local_sha256 = hashlib.sha256(data).hexdigest()
|
||||
remote_path = f"{PROD_UPLOADS_TTS}/{post_id}.mp3"
|
||||
_ssh_upload_bytes(data, remote_path)
|
||||
# Verificación de tamaño+contenido: un rc=0 de ssh no acredita que el asset
|
||||
# sea el mismo. El hash se persiste como meta y permite playback verificado.
|
||||
# Verificación de tamaño: no fiarse ciegamente del rc=0 de ssh. `wc -c` debe
|
||||
# correr (y resolver la redirección) DENTRO del contenedor — ver _remote_wrap.
|
||||
remote_size = int(_ssh_text(_remote_wrap(f"wc -c < {remote_path}")).strip())
|
||||
if remote_size != len(data):
|
||||
raise RuntimeError(f"tamaño no coincide tras subir #{post_id}: local={len(data)} remoto={remote_size}")
|
||||
remote_sha256 = _ssh_text(_remote_wrap(f"sha256sum {remote_path} | cut -d' ' -f1")).strip()
|
||||
if remote_sha256 != local_sha256:
|
||||
raise RuntimeError(f"SHA-256 no coincide tras subir #{post_id}: local={local_sha256} remoto={remote_sha256}")
|
||||
return local_sha256
|
||||
|
||||
|
||||
def prod_remove_mp3(post_id: int) -> None:
|
||||
@@ -191,9 +185,8 @@ def sync_one(post_id: int, state: dict, *, dry_run: bool) -> str:
|
||||
return "PLAN: subiría mp3 + setaudio"
|
||||
|
||||
voice = local_meta(post_id, "fea_audio_voice") or "NicoFeadulta2026"
|
||||
sha256 = prod_upload_mp3(post_id)
|
||||
prod_upload_mp3(post_id)
|
||||
prod_helper("setaudio", str(post_id), f"/wp-content/uploads/tts/{post_id}.mp3", voice)
|
||||
prod_helper("setflag", str(post_id), "fea_audio_sha256", sha256)
|
||||
if post_id not in state["synced"]:
|
||||
state["synced"].append(post_id)
|
||||
save_state(state)
|
||||
|
||||
Reference in New Issue
Block a user