diff --git a/index.html b/index.html index 29b776c..e2fd6bc 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,16 @@

Crochet

-
+
+ + diff --git a/js/dnd.js b/js/dnd.js index e40e7b2..67b1271 100644 --- a/js/dnd.js +++ b/js/dnd.js @@ -4,6 +4,39 @@ document.addEventListener('DOMContentLoaded', () => { const panelTop = document.getElementById('panel-top'); // Todo el panel "Secciones" (incluye los botones "+ tipo"), usado como zona de detección al reordenar. const sectionsContainer = document.getElementById('panel-top-sections'); // Contiene las secciones que el usuario va añadiendo. const draggableItemsSource = document.getElementById('draggable-items-source'); // Lista oculta con los elementos disponibles para arrastrar: fuente de datos única, se clona (visible) en cada sección de tipo "arrastrar y soltar". + const languageSelect = document.getElementById('language-select'); // Idioma del contenido (no de la interfaz): controla qué versión de los textos de título/subtítulo/texto se muestra y edita. + + // Idioma actualmente seleccionado para el contenido. La estructura de + // secciones (cuáles hay, su orden, su tipo) es la misma para todos los + // idiomas; lo único que cambia es qué texto se muestra en cada campo de + // título/subtítulo/texto libre. + let currentLanguage = languageSelect.value; + + // --------------------------------------------------------------------- + // Multi-idioma del contenido: título, subtítulo y texto libre guardan un + // texto por idioma en vez de uno solo, para poder editarlos y asociarlos + // al idioma seleccionado sin perder lo ya escrito en los demás. + // --------------------------------------------------------------------- + + // Cada input/textarea traducible guarda su propio mapa { idioma: texto } + // serializado en el propio elemento (data-translations), así viaja solo + // con guardar/cargar la sección (ver serializeSection/addTitleSection...), + // sin necesidad de un almacén aparte que haya que mantener sincronizado. + function getTranslations(el) { + try { + return JSON.parse(el.dataset.translations || '{}'); + } catch { + return {}; + } + } + + // Actualiza, dentro del mapa ya guardado en el elemento, el texto del + // idioma indicado (por defecto el actual), sin tocar el resto de idiomas. + function setTranslation(el, text, lang = currentLanguage) { + const translations = getTranslations(el); + translations[lang] = text; + el.dataset.translations = JSON.stringify(translations); + } // --------------------------------------------------------------------- // Reordenar secciones (arrastrando toda la cabecera de cada una) @@ -407,10 +440,31 @@ document.addEventListener('DOMContentLoaded', () => { // añadir/quitar/reordenar secciones o elementos soltados (MutationObserver). // Al estar en #panel-top-sections y usar subtree:true, esto ya cubre // también los cambios dentro de las secciones anidadas en un "Grupo". - sectionsContainer.addEventListener('input', renderOutput); + // Si el campo es traducible (título/subtítulo/texto), cada pulsación de + // tecla también se guarda en su mapa de traducciones para el idioma + // actual, así no hace falta un paso aparte de "guardar" al cambiar de + // idioma o al serializar la sección. + sectionsContainer.addEventListener('input', (e) => { + if (e.target.matches('.title-input, .subtitle-input, textarea')) { + setTranslation(e.target, e.target.value); + } + renderOutput(); + }); sectionsContainer.addEventListener('change', renderOutput); new MutationObserver(renderOutput).observe(sectionsContainer, { childList: true, subtree: true }); + // Al cambiar el idioma del contenido, se sustituye en cada campo + // traducible (a cualquier nivel de anidamiento) el texto mostrado por el + // que tenga guardado para el nuevo idioma (vacío si aún no se ha escrito + // nada en ese idioma), y se repinta la vista previa. + languageSelect.addEventListener('change', () => { + currentLanguage = languageSelect.value; + sectionsContainer.querySelectorAll('.title-input, .subtitle-input, textarea').forEach(el => { + el.value = getTranslations(el)[currentLanguage] || ''; + }); + renderOutput(); + }); + // --------------------------------------------------------------------- // Creación de secciones (título, texto, imagen, drag & drop, grupo) // --------------------------------------------------------------------- @@ -544,17 +598,20 @@ document.addEventListener('DOMContentLoaded', () => { // Sección de título: un input de una línea que se muestra como

en el // resultado. `data` es el dataset del botón "+ Título" (data-type-label, - // data-placeholder). `text`, si se pasa, precarga el valor (se usa al - // restaurar un patrón guardado). `container` es dónde se añade la - // sección: #panel-top-sections por defecto, o el de un grupo. - function addTitleSection(data, text = '', container = sectionsContainer) { + // data-placeholder). `translations`, si se pasa, es el mapa { idioma: + // texto } completo (se usa al restaurar un patrón guardado o al + // duplicar una sección); se muestra el que corresponda al idioma actual. + // `container` es dónde se añade la sección: #panel-top-sections por + // defecto, o el de un grupo. + function addTitleSection(data, translations = {}, container = sectionsContainer) { const { section, body } = createSectionShell(data.typeLabel); const input = document.createElement('input'); input.type = 'text'; input.className = 'title-input input input-bordered w-full'; input.placeholder = data.placeholder; - input.value = text; + input.dataset.translations = JSON.stringify(translations); + input.value = translations[currentLanguage] || ''; body.appendChild(input); container.appendChild(section); @@ -562,27 +619,29 @@ document.addEventListener('DOMContentLoaded', () => { // Sección de subtítulo: igual que la de título pero se muestra como

// (más pequeño) en el resultado, para marcar un encabezado secundario. - function addSubtitleSection(data, text = '', container = sectionsContainer) { + function addSubtitleSection(data, translations = {}, container = sectionsContainer) { const { section, body } = createSectionShell(data.typeLabel); const input = document.createElement('input'); input.type = 'text'; input.className = 'subtitle-input input input-bordered w-full'; input.placeholder = data.placeholder; - input.value = text; + input.dataset.translations = JSON.stringify(translations); + input.value = translations[currentLanguage] || ''; body.appendChild(input); container.appendChild(section); } // Sección de texto libre. - function addTextSection(data, text = '', container = sectionsContainer) { + function addTextSection(data, translations = {}, container = sectionsContainer) { const { section, body } = createSectionShell(data.typeLabel); const textarea = document.createElement('textarea'); textarea.className = 'textarea textarea-bordered w-full'; textarea.placeholder = data.placeholder; - textarea.value = text; + textarea.dataset.translations = JSON.stringify(translations); + textarea.value = translations[currentLanguage] || ''; body.appendChild(textarea); container.appendChild(section); @@ -809,14 +868,17 @@ document.addEventListener('DOMContentLoaded', () => { return { type: 'group', children: Array.from(groupContainer.children).map(serializeSection).filter(Boolean) }; } + // Título/subtítulo/texto libre: se serializa el mapa de traducciones + // completo (todos los idiomas ya escritos), no solo el texto que se ve + // ahora mismo, para no perder lo escrito en otros idiomas al guardar. const titleInput = section.querySelector('.title-input'); - if (titleInput) return { type: 'title', text: titleInput.value }; + if (titleInput) return { type: 'title', text: getTranslations(titleInput) }; const subtitleInput = section.querySelector('.subtitle-input'); - if (subtitleInput) return { type: 'subtitle', text: subtitleInput.value }; + if (subtitleInput) return { type: 'subtitle', text: getTranslations(subtitleInput) }; const textarea = section.querySelector('textarea'); - if (textarea) return { type: 'text', text: textarea.value }; + if (textarea) return { type: 'text', text: getTranslations(textarea) }; const dndCanvas = section.querySelector('.dnd-canvas'); if (dndCanvas) {