Wazeopedia Blocks-Library

Biblioteca con la lógica para los bloques de contenido de Wazeopedia.

بۇ قوليازمىنى بىۋاسىتە قاچىلاشقا بولمايدۇ. بۇ باشقا قوليازمىلارنىڭ ئىشلىتىشى ئۈچۈن تەمىنلەنگەن ئامبار بولۇپ، ئىشلىتىش ئۈچۈن مېتا كۆرسەتمىسىگە قىستۇرىدىغان كود: // @require https://update.greasyfork.ip-ddns.com/scripts/538615/1604422/Wazeopedia%20Blocks-Library.js

// ==UserScript==
// @name         Wazeopedia Blocks Library
// @namespace    http://tampermonkey.net/
// @version      9.0.2.2
// @description  Biblioteca de lógica para bloques de contenido de Wazeopedia (Título, Bio, FAQ, etc.).
// @author       Annthizze
// @require      https://update.greasyfork.ip-ddns.com/scripts/538610/Wazeopedia%20Core%20UI%20Library.js
// @require      https://update.greasyfork.ip-ddns.com/scripts/538744/Wazeopedia%20Content%20Library.js
// @grant        GM_info
// @license      MIT
// ==/UserScript==

'use strict';
(function() {
    if (window.WazeopediaBlocks) return;
    
    window.WazeopediaBlocks = (function() {
        const UI = window.WazeopediaUI;
        const Content = window.WazeopediaContent;

        // --- DEFINICIÓN DE TODAS LAS FUNCIONES Y CONSTANTES PRIVADAS ---

        const ensureProperSpacing = (currentText, newBlockText, position, relativeBlockData) => { let before = "", after = "", middle = newBlockText; const twoNewlines = "\n\n"; switch (position) { case 'start': before = ""; after = currentText; if (after.trim().length > 0 && !middle.endsWith(twoNewlines) && !after.startsWith("\n")) { middle += (middle.endsWith("\n") ? "\n" : twoNewlines); } else if (after.trim().length > 0 && middle.endsWith("\n") && !middle.endsWith(twoNewlines) && !after.startsWith("\n")){ middle += "\n"; } break; case 'end': before = currentText; after = ""; if (before.trim().length > 0 && !middle.startsWith(twoNewlines) && !before.endsWith("\n")) { middle = (before.endsWith("\n") ? "\n" : twoNewlines) + middle; } else if (before.trim().length > 0 && !middle.startsWith(twoNewlines) && before.endsWith("\n") && !before.endsWith(twoNewlines) ){ middle = "\n" + middle; } break; case 'afterRelative': if (!relativeBlockData) return ensureProperSpacing(currentText, newBlockText, 'start'); before = currentText.substring(0, relativeBlockData.endIndex); after = currentText.substring(relativeBlockData.endIndex); if (!before.endsWith(twoNewlines) && !before.endsWith("\n")) middle = twoNewlines + middle; else if (before.endsWith("\n") && !before.endsWith(twoNewlines) && !middle.startsWith("\n")) middle = "\n" + middle; if (after.trim().length > 0 && !middle.endsWith(twoNewlines) && !after.startsWith("\n")) { middle += (middle.endsWith("\n") ? "\n" : twoNewlines); } else if (after.trim().length > 0 && middle.endsWith("\n") && !middle.endsWith(twoNewlines) && !after.startsWith("\n")){ middle += "\n"; } break; default: return { textToInsert: newBlockText.trim(), cursorPosition: newBlockText.trim().length }; } return { textToInsert: before + middle + after, cursorPosition: (before + middle).length }; };
        const generateBodyContentAndTitleParams = (cleanedPostTitleForDisplay) => { const isEditing = window.location.href.includes('/t/'); let linkUrl = isEditing ? window.location.href : `https://www.waze.com/discuss/t/${cleanedPostTitleForDisplay.toLowerCase().replace(/\s+/g, '-').replace(/-+/g, '-').replace(/[^a-z0-9-]/g, '')}`; const markdownEscapedPostTitle = cleanedPostTitleForDisplay.replace(/([\[\]\(\)])/g, '\\$1'); return { bodyContentText: `[${markdownEscapedPostTitle}](${linkUrl})`, urlEncodedTitleForNewTopic: encodeURIComponent(cleanedPostTitleForDisplay) }; };
        const generateFullForumBlock = (bodyContentTextForTemplate, urlEncodedNewTopicTitle) => { const bodyParamUnencoded = Content.FORUM_BLOCK.BODY_TEMPLATE(bodyContentTextForTemplate); const newTopicUrl = `https://www.waze.com/discuss/new-topic?category=spain-usuarios-y-editores/wazeopedia-es/4779&title=WAZO%20-%20${urlEncodedNewTopicTitle}&body=${encodeURIComponent(bodyParamUnencoded)}`; return `---
${Content.FORUM_BLOCK.IMAGE}
${Content.FORUM_BLOCK.IDENTIFIER}
${Content.FORUM_BLOCK.LINK_TEXT_TEMPLATE.replace('{{NEW_TOPIC_URL}}', newTopicUrl)}`; };
        const getForumBlockRegex = () => new RegExp(`(?:^|\\n)---` + `\\s*${Content.FORUM_BLOCK.IMAGE.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}` + `\\s*${Content.FORUM_BLOCK.IDENTIFIER.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}` + `[\\s\\S]*?` + `href="https://www\\.waze\\.com/discuss/new-topic\\?category=spain-usuarios-y-editores/wazeopedia-es/4779[^"]*">→aquí←</a>`, 'm');
        const parseExistingTitleBlock = (editorText) => { if (!editorText.startsWith(Content.TITLE_BLOCK.TOC_MARKER)) return null; const wzBoxStartIndex = editorText.indexOf(Content.TITLE_BLOCK.WZBOX_START); if (wzBoxStartIndex === -1) return null; const wzBoxEndIndex = editorText.indexOf(Content.TITLE_BLOCK.WZBOX_END, wzBoxStartIndex); if (wzBoxEndIndex === -1) return null; const content = editorText.substring(wzBoxStartIndex + Content.TITLE_BLOCK.WZBOX_START.length, wzBoxEndIndex); const titleMatch = content.match(/\[center\]\[wzh=1\](.*?)\[\/wzh\]\[\/center\]/); const title = titleMatch ? titleMatch[1].trim() : ""; let statusKey = "aprobado", forumUrl = ""; for (const key in Content.TITLE_BLOCK.STATUS_OPTIONS) { if (content.includes(Content.TITLE_BLOCK.STATUS_OPTIONS[key].text.split('***')[1])) { statusKey = key; if (Content.TITLE_BLOCK.STATUS_OPTIONS[key].requiresUrl) { const urlMatch = content.match(/\[→foro←\]\(([^)]+)\)/); forumUrl = urlMatch ? urlMatch[1] : ""; } break; } } return { title, statusKey, forumUrl, startIndex: 0, endIndex: wzBoxEndIndex + Content.TITLE_BLOCK.WZBOX_END.length }; };
        const parseExistingIntroductionBlock = (editorText) => { const fullHeaderSearchIndex = editorText.indexOf(Content.INTRO_BLOCK.HEADER); if (fullHeaderSearchIndex === -1) return null; const contentStartAfterFullHeader = fullHeaderSearchIndex + Content.INTRO_BLOCK.HEADER.length; if (!editorText.substring(contentStartAfterFullHeader).startsWith("\n\n")) return null; const actualMainTextStartIndex = contentStartAfterFullHeader + 2; let endOfBlockIndex = editorText.indexOf("\n\n---", actualMainTextStartIndex); if(endOfBlockIndex === -1) return null; const blockContentBetween = editorText.substring(actualMainTextStartIndex, endOfBlockIndex); let mainText = blockContentBetween, noteText = "", additionalText = "", hasNote = false, hasAdditional = false; const noteBlockPattern = "\n\n" + Content.INTRO_BLOCK.NOTE_PREFIX; const noteStartIndex = blockContentBetween.indexOf(noteBlockPattern); if (noteStartIndex !== -1) { hasNote = true; mainText = blockContentBetween.substring(0, noteStartIndex).trim(); const afterNotePrefix = blockContentBetween.substring(noteStartIndex + noteBlockPattern.length); const additionalTextSeparator = "\n\n"; const additionalTextIndex = afterNotePrefix.indexOf(additionalTextSeparator); if (additionalTextIndex !== -1) { noteText = afterNotePrefix.substring(0, additionalTextIndex).trim(); additionalText = afterNotePrefix.substring(additionalTextIndex + additionalTextSeparator.length).trim(); if (additionalText) hasAdditional = true; } else { noteText = afterNotePrefix.trim(); } } else { mainText = blockContentBetween.trim(); } return { mainText, noteText, additionalText, hasNote, hasAdditional, startIndex: fullHeaderSearchIndex, endIndex: endOfBlockIndex + "\n\n---".length }; };
        const MONTHS_ES = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'].join('|');
        const dateRegexDayMonthYear = new RegExp(`^((?:3[01]|[12][0-9]|0?[1-9]) de (?:${MONTHS_ES}) de (?:\\d{4}))`, 'i');
        const dateRegexMonthYear = new RegExp(`^((?:${MONTHS_ES}) de (?:\\d{4}))`, 'i');
        const dateRegexYear = new RegExp(`^(\\d{4})(?=\\s|$)`);
        const isValidBioDate = (dateText) => { const text = dateText.trim(); return dateRegexDayMonthYear.test(text) || dateRegexMonthYear.test(text) || /^\d{4}$/.test(text); };
        const getBioEntryPrefix = (dateText) => { const text = (dateText || "").trim().toLowerCase(); if (dateRegexDayMonthYear.test(text)) return "El "; if (dateRegexMonthYear.test(text)) return "En "; if (/^\d{4}$/.test(text)) return "En el año "; return ""; };
        const parseExistingBiographyBlock = (editorText) => { const blockStartIndex = editorText.indexOf(Content.BIO_BLOCK.HEADER); if (blockStartIndex === -1) return null; const contentStartIndex = blockStartIndex + Content.BIO_BLOCK.HEADER.length; const nextBlockRegex = /(?:\n\n---|# \[wzh=[12]\]|Foro de discusión:)/; const nextBlockMatch = editorText.substring(contentStartIndex).match(nextBlockRegex); const endIndex = nextBlockMatch ? contentStartIndex + nextBlockMatch.index : editorText.length; const blockContent = editorText.substring(contentStartIndex, endIndex).trim(); const entries = []; if (blockContent) { blockContent.split('\n').forEach(line => { if (!line.startsWith('* ')) return; let core = line.substring(2).trim(); const prefixMatch = core.match(/^(?:El |En el año |En )/); if (prefixMatch) { core = core.substring(prefixMatch[0].length); } const linkMatch = core.match(/^\[([^\]]+)\]\(([^)]+)\)\s*(.*)/); if (linkMatch) { entries.push({ dateText: linkMatch[1], url: linkMatch[2], description: linkMatch[3].replace(/\.$/, '') }); } else { let dateText = ''; let description = ''; const dmyMatch = core.match(dateRegexDayMonthYear); const myMatch = core.match(dateRegexMonthYear); const yMatch = core.match(dateRegexYear); let foundMatch = null; if (dmyMatch && core.startsWith(dmyMatch[0])) { foundMatch = dmyMatch[0]; } else if (myMatch && core.startsWith(myMatch[0])) { foundMatch = myMatch[0]; } else if (yMatch && core.startsWith(yMatch[0])) { foundMatch = yMatch[0]; } if (foundMatch) { dateText = foundMatch; description = core.substring(foundMatch.length).trim(); } else { dateText = core; description = ''; } entries.push({ dateText, url: '', description: description.replace(/\.$/, '') }); } }); } return { entries, startIndex: blockStartIndex, endIndex }; };
        const createBioEntryElement = (entry = { dateText: '', url: '', description: '' }, index, container) => { const details = document.createElement('details'); details.className = 'wz-bio-entry'; details.name = 'bio-accordion'; const summary = document.createElement('summary'); summary.appendChild(document.createTextNode(entry.dateText || `Entrada ${index + 1}`)); const contentDiv = document.createElement('div'); contentDiv.className = 'wz-bio-entry-content'; contentDiv.innerHTML = `<label>Fecha (texto):</label><input type="text" class="wz-bio-date" placeholder="DD de MES de AAAA | MES de AAAA | AAAA" value="${entry.dateText}"><label>URL (opcional):</label><input type="text" class="wz-bio-url" placeholder="https://ejemplo.com" value="${entry.url}"><label>Descripción:</label><textarea class="wz-bio-desc">${entry.description}</textarea><div class="wz-bio-preview-label">Previsualización:</div><div class="wz-bio-entry-preview"></div>`; const removeBtn = UI.createButton('Eliminar', 'wz-bio-remove-btn', () => { details.remove(); updateBioSummaries(container); }); summary.appendChild(removeBtn); details.append(summary, contentDiv); const dateInput = contentDiv.querySelector('.wz-bio-date'), urlInput = contentDiv.querySelector('.wz-bio-url'), descInput = contentDiv.querySelector('.wz-bio-desc'), preview = contentDiv.querySelector('.wz-bio-entry-preview'); const updateFn = () => { const dateText = dateInput.value.trim(); const url = urlInput.value.trim(); const description = descInput.value.trim(); const prefix = getBioEntryPrefix(dateText); let descWithPeriod = description; if (descWithPeriod && !/[.!?]$/.test(descWithPeriod)) descWithPeriod += '.'; const dateHtml = url ? `<a href="#" onclick="return false;">${dateText || 'Fecha'}</a>` : (dateText || 'Fecha'); preview.innerHTML = `<ul><li>${prefix}${dateHtml}${description ? ' ' + descWithPeriod : ''}</li></ul>`; summary.firstChild.textContent = dateText || `Entrada ${Array.from(container.children).indexOf(details) + 1}`; }; [dateInput, urlInput, descInput].forEach(el => el.addEventListener('input', updateFn)); updateFn(); return details; };
        const updateBioSummaries = (container) => { container.querySelectorAll('details.wz-bio-entry').forEach((details, idx) => { const dateInput = details.querySelector('.wz-bio-date'); details.querySelector('summary').firstChild.textContent = dateInput.value.trim() || `Entrada ${idx + 1}`; }); };
        const parseExistingFaqBlock = (editorText) => { const FAQ_BLOCK_REGEX = /(?:^|\n)---\s*\n+# \[wzh=1\]Preguntas Frecuentes\[\/wzh\]\s*\n+([\s\S]*?)\n+---\s*(?:\n|$)/; const match = editorText.match(FAQ_BLOCK_REGEX); if (!match) return null; const content = match[1]; const entries = []; const questionRegex = /\*\*🔹 (.*?)\*\*\s*\n(.*?)(?=\n\n\*\*🔹|$(?![\r\n]))/gs; let qaMatch; while ((qaMatch = questionRegex.exec(content)) !== null) { entries.push({ question: qaMatch[1].trim(), answer: qaMatch[2].trim() }); } return { entries, startIndex: match.index === 0 ? 0 : match.index + 1, endIndex: match.index === 0 ? match[0].length : match.index + match[0].length }; };
        const createFaqEntryElement = (entry = { question: '', answer: '' }, index, container) => { const details = document.createElement('details'); details.className = 'wz-faq-entry'; details.name = 'faq-accordion'; const summary = document.createElement('summary'); summary.textContent = entry.question || `FAQ #${index + 1}`; const contentDiv = document.createElement('div'); contentDiv.className = 'wz-faq-entry-content'; contentDiv.innerHTML = `<label>Pregunta:</label><input type="text" class="wz-faq-question" placeholder="Escribe la pregunta..." value="${entry.question.replace(/"/g, '"')}"><label>Respuesta:</label><textarea class="wz-faq-answer" placeholder="Escribe la respuesta...">${entry.answer}</textarea><div class="wz-faq-preview-label">Previsualización:</div><div class="wz-faq-entry-preview"></div>`; const removeBtn = UI.createButton('Eliminar', 'wz-faq-remove-btn', () => { details.remove(); container.querySelectorAll('.wz-faq-entry summary').forEach((s, i) => { const qInput = s.nextElementSibling.querySelector('.wz-faq-question'); if (!s.textContent.startsWith('FAQ #')) return; if (!qInput.value.trim()) s.textContent = `FAQ #${i + 1}`; }); }); summary.appendChild(removeBtn); details.append(summary, contentDiv); const questionInput = contentDiv.querySelector('.wz-faq-question'); const answerInput = contentDiv.querySelector('.wz-faq-answer'); const previewElement = contentDiv.querySelector('.wz-faq-entry-preview'); UI.createFormattingToolbar(answerInput, ['bold', 'italic', 'link', 'quote', 'emoji']); const updateFn = () => { const question = questionInput.value.trim(); const answer = answerInput.value.trim(); let previewHtml = answer.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>').replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\*(.*?)\*/g, '<em>$1</em>').replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>').replace(/\n/g, '<br>'); previewElement.innerHTML = `<strong>🔹 ${question || 'Pregunta'}</strong><br>${previewHtml || 'Respuesta...'}`; summary.firstChild.textContent = questionInput.value.trim() || `FAQ #${Array.from(container.children).indexOf(details) + 1}`; }; [questionInput, answerInput].forEach(el => el.addEventListener('input', updateFn)); updateFn(); return details; };
        const showForumUpdateConfirmModal = (textarea, existingBlockInfo, newParams, currentParams) => { UI.closeAllModals(); const overlay = document.createElement('div'); overlay.className = 'wz-modal-overlay'; const modalContent = document.createElement('div'); modalContent.className = 'wz-modal-content'; let htmlContent = `<h3>Estado del Bloque de Discusión</h3><div class="wz-modal-scrollable-content">`; let needsUpdate = false; if (!existingBlockInfo) { htmlContent += `<p>El bloque 'Foro de discusión' no existe en el editor.</p>`; } else { const titleMatches = currentParams.urlEncodedTitle === newParams.urlEncodedTitleForNewTopic; htmlContent += `<div class="wz-forum-update-modal-item"><p><span class="status-icon ${titleMatches ? 'wz-status-ok">✔️' : 'wz-status-mismatch">❌'}</span><strong class="label"> TÍTULO DEL TEMA</strong></p>${!titleMatches ? `<p><span class="label">Actual:</span> <span class="value">${decodeURIComponent(currentParams.urlEncodedTitle || 'No encontrado')}</span></p>` : ''}<p><span class="label">Esperado:</span> <span class="value">${decodeURIComponent(newParams.urlEncodedTitleForNewTopic)}</span></p></div>`; if (!titleMatches) needsUpdate = true; const bodyContentMatches = decodeURIComponent(currentParams.bodyContent) === newParams.bodyContentText; htmlContent += `<div class="wz-forum-update-modal-item"><p><span class="status-icon ${bodyContentMatches ? 'wz-status-ok">✔️' : 'wz-status-mismatch">❌'}</span><strong class="label"> ENLACE</strong></p>${!bodyContentMatches ? `<p><span class="label">Actual:</span> <span class="value">${decodeURIComponent(currentParams.bodyContent || 'No encontrado')}</span></p>` : ''}<p><span class="label">Esperado:</span> <span class="value">${newParams.bodyContentText}</span></p></div>`; if (!bodyContentMatches) needsUpdate = true; if (!needsUpdate) htmlContent += `<p style="text-align:center; color:green; margin-top:15px;">El bloque ya está actualizado.</p>`; } htmlContent += `</div>`; modalContent.innerHTML = htmlContent; const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'wz-modal-buttons'; if (!existingBlockInfo) { buttonsDiv.appendChild(UI.createButton('Insertar Bloque', 'wz-confirm', () => { const fullBlock = generateFullForumBlock(newParams.bodyContentText, newParams.urlEncodedTitleForNewTopic); const { textToInsert: finalContent, cursorPosition } = ensureProperSpacing(textarea.value, fullBlock, 'end'); textarea.value = finalContent; textarea.selectionStart = textarea.selectionEnd = cursorPosition; textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); UI.closeAllModals(); })); } else if (needsUpdate) { buttonsDiv.appendChild(UI.createButton('Actualizar Bloque', 'wz-confirm', () => { const updatedFullBlock = generateFullForumBlock(newParams.bodyContentText, newParams.urlEncodedTitleForNewTopic); textarea.value = textarea.value.substring(0, existingBlockInfo.startIndex) + updatedFullBlock + textarea.value.substring(existingBlockInfo.endIndex); textarea.selectionStart = textarea.selectionEnd = existingBlockInfo.startIndex + updatedFullBlock.length; textarea.focus(); textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); UI.closeAllModals(); })); } else { buttonsDiv.appendChild(UI.createButton('Aceptar', 'wz-confirm', UI.closeAllModals)); } buttonsDiv.appendChild(UI.createButton('Cancelar', 'wz-cancel', UI.closeAllModals)); modalContent.appendChild(buttonsDiv); overlay.appendChild(modalContent); document.body.appendChild(overlay); UI.setupModalEscape(overlay, UI.closeAllModals); };
        
        return {
            showTitleConfigModal: function(textarea) { UI.closeAllModals(); const existingData = parseExistingTitleBlock(textarea.value); const initial = existingData || { title: "", statusKey: "aprobado", forumUrl: "" }; const overlay = document.createElement('div'); overlay.className = 'wz-modal-overlay'; const modalContent = document.createElement('div'); modalContent.className = 'wz-modal-content'; modalContent.innerHTML = `<h3>Configurar Título y Estado</h3><div class="wz-modal-scrollable-content"><div class="wz-title-modal-error" style="display:none;"></div><label for="wz-title-main">Título Artículo:</label><input type="text" id="wz-title-main" value="${initial.title}"><label for="wz-title-status-select">Estado Artículo:</label><select id="wz-title-status-select">${Object.keys(Content.TITLE_BLOCK.STATUS_OPTIONS).map(k => `<option value="${k}" ${initial.statusKey === k ? 'selected' : ''}>${Content.TITLE_BLOCK.STATUS_OPTIONS[k].label}</option>`).join('')}</select><div id="wz-title-forum-url-section" style="display: ${Content.TITLE_BLOCK.STATUS_OPTIONS[initial.statusKey]?.requiresUrl ? 'block' : 'none'};"><label for="wz-title-forum-url">URL Foro:</label><input type="text" id="wz-title-forum-url" placeholder="https://..." value="${initial.forumUrl}"></div></div>`; const errorDiv = modalContent.querySelector('.wz-title-modal-error'), titleInput = modalContent.querySelector('#wz-title-main'), statusSelect = modalContent.querySelector('#wz-title-status-select'), forumUrlSection = modalContent.querySelector('#wz-title-forum-url-section'), forumUrlInput = modalContent.querySelector('#wz-title-forum-url'); statusSelect.onchange = () => forumUrlSection.style.display = Content.TITLE_BLOCK.STATUS_OPTIONS[statusSelect.value]?.requiresUrl ? 'block' : 'none'; const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'wz-modal-buttons'; const saveBtn = UI.createButton(existingData ? 'Actualizar Bloque' : 'Insertar Bloque', 'wz-confirm', () => { const title = titleInput.value.trim(), statusKey = statusSelect.value, forumUrl = forumUrlInput.value.trim(); if (!title) { errorDiv.textContent = "Título no puede estar vacío."; errorDiv.style.display = 'block'; return; } if (Content.TITLE_BLOCK.STATUS_OPTIONS[statusKey]?.requiresUrl && !forumUrl) { errorDiv.textContent = 'URL de foro requerida.'; errorDiv.style.display = 'block'; return; } const statusText = Content.TITLE_BLOCK.STATUS_OPTIONS[statusKey].text.replace("{{FORUM_URL}}", forumUrl); const newBlock = [Content.TITLE_BLOCK.TOC_MARKER, `\n\n${Content.TITLE_BLOCK.WZBOX_START}\n`, Content.TITLE_BLOCK.IMAGE, `\n[center][wzh=1]${title}[/wzh][/center]\n\n`, statusText, `\n${Content.TITLE_BLOCK.WZBOX_END}`].join(''); if (existingData) { textarea.value = newBlock + textarea.value.substring(existingData.endIndex); } else { const { textToInsert, cursorPosition } = ensureProperSpacing(textarea.value, newBlock, 'start'); textarea.value = textToInsert; textarea.selectionStart = textarea.selectionEnd = cursorPosition; } UI.closeAllModals(); }); buttonsDiv.append(UI.createButton('Cancelar', 'wz-cancel', UI.closeAllModals), saveBtn); modalContent.querySelector('.wz-modal-scrollable-content').after(buttonsDiv); overlay.appendChild(modalContent); document.body.appendChild(overlay); UI.setupModalEscape(overlay, UI.closeAllModals); setTimeout(() => titleInput.focus(), 100); },
            showIntroductionConfigModal: function(textarea) { UI.closeAllModals(); const existingBlockData = parseExistingIntroductionBlock(textarea.value); const initialData = existingBlockData || { mainText: "", noteText: "", additionalText: "", hasNote: false, hasAdditional: false }; const overlay = document.createElement('div'); overlay.className = 'wz-modal-overlay'; const content = document.createElement('div'); content.className = 'wz-modal-content'; content.innerHTML = `<h3>Configurar Bloque de Introducción</h3><div class="wz-modal-scrollable-content"><label for="wz-intro-main">Texto Principal:</label><textarea id="wz-intro-main"></textarea><div class="wz-checkbox-group"><input type="checkbox" id="wz-intro-add-note-check"><label for="wz-intro-add-note-check">Añadir nota</label></div><div id="wz-intro-note-section" class="wz-hidden-section"><label for="wz-intro-note">Texto de Nota (${Content.INTRO_BLOCK.NOTE_PREFIX.trim()} se añadirá):</label><textarea id="wz-intro-note" placeholder="Ej: Edición limitada..."></textarea></div><div class="wz-checkbox-group"><input type="checkbox" id="wz-intro-add-additional-check"><label for="wz-intro-add-additional-check">Añadir texto adicional</label></div><div id="wz-intro-additional-section" class="wz-hidden-section"><label for="wz-intro-additional">Texto Adicional:</label><textarea id="wz-intro-additional"></textarea></div></div>`; const mainTextEl = content.querySelector('#wz-intro-main'), addNoteCheckEl = content.querySelector('#wz-intro-add-note-check'), noteSectionEl = content.querySelector('#wz-intro-note-section'), noteTextEl = content.querySelector('#wz-intro-note'), addAdditionalCheckEl = content.querySelector('#wz-intro-add-additional-check'), additionalSectionEl = content.querySelector('#wz-intro-additional-section'), additionalTextEl = content.querySelector('#wz-intro-additional'); mainTextEl.value = initialData.mainText; noteTextEl.value = initialData.noteText; additionalTextEl.value = initialData.additionalText; addNoteCheckEl.checked = initialData.hasNote; if (initialData.hasNote) noteSectionEl.style.display = 'block'; addNoteCheckEl.onchange = () => noteSectionEl.style.display = addNoteCheckEl.checked ? 'block' : 'none'; addAdditionalCheckEl.checked = initialData.hasAdditional; if (initialData.hasAdditional) additionalSectionEl.style.display = 'block'; addAdditionalCheckEl.onchange = () => additionalSectionEl.style.display = addAdditionalCheckEl.checked ? 'block' : 'none'; const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'wz-modal-buttons'; const saveBtn = UI.createButton(existingBlockData ? 'Actualizar Bloque' : 'Insertar Bloque', 'wz-confirm', () => { let blockParts = [Content.INTRO_BLOCK.HEADER, "\n\n" + mainTextEl.value.trim()]; if (addNoteCheckEl.checked) blockParts.push("\n\n" + Content.INTRO_BLOCK.NOTE_PREFIX + noteTextEl.value.trim()); if (addAdditionalCheckEl.checked) blockParts.push("\n\n" + additionalTextEl.value.trim()); blockParts.push("\n\n---"); const finalBlock = blockParts.join(''); if (existingBlockData) { textarea.value = textarea.value.substring(0, existingBlockData.startIndex) + finalBlock + textarea.value.substring(existingBlockData.endIndex); } else { const titleBlockData = parseExistingTitleBlock(textarea.value); const { textToInsert, cursorPosition } = ensureProperSpacing(textarea.value, finalBlock, titleBlockData ? 'afterRelative' : 'start', titleBlockData); textarea.value = textToInsert; textarea.selectionStart = textarea.selectionEnd = cursorPosition; } textarea.focus(); textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); UI.closeAllModals(); }); buttonsDiv.append(UI.createButton('Cancelar', 'wz-cancel', UI.closeAllModals), saveBtn); content.querySelector('.wz-modal-scrollable-content').after(buttonsDiv); overlay.appendChild(content); document.body.appendChild(overlay); UI.setupModalEscape(overlay, UI.closeAllModals); setTimeout(() => mainTextEl.focus(), 100); },
            showBiographyConfigModal: function(textarea) { UI.closeAllModals(); const existingBlock = parseExistingBiographyBlock(textarea.value); const entries = existingBlock && existingBlock.entries.length > 0 ? existingBlock.entries : [{ dateText: '', url: '', description: '' }]; const overlay = document.createElement('div'); overlay.className = 'wz-modal-overlay'; const modalContent = document.createElement('div'); modalContent.className = 'wz-modal-content'; modalContent.innerHTML = `<h3>Configurar Biografía y Enlaces</h3><div class="wz-bio-modal-error" style="display:none;"></div><div class="wz-modal-scrollable-content"><div id="wz-bio-entry-list"></div></div>`; const errorDiv = modalContent.querySelector('.wz-bio-modal-error'); const entryList = modalContent.querySelector('#wz-bio-entry-list'); entries.forEach((entry, i) => entryList.appendChild(createBioEntryElement(entry, i, entryList))); const addBtn = UI.createButton('Añadir Entrada', 'wz-bio-add-entry-btn wz-confirm', () => { if (entryList.children.length < Content.BIO_BLOCK.MAX_ENTRIES) { const newEl = createBioEntryElement(undefined, entryList.children.length, entryList); entryList.appendChild(newEl); newEl.open = true; newEl.scrollIntoView({ behavior: 'smooth' }); } else { errorDiv.textContent = `Máximo ${Content.BIO_BLOCK.MAX_ENTRIES} entradas.`; errorDiv.style.display = 'block'; } }); modalContent.querySelector('.wz-modal-scrollable-content').appendChild(addBtn); const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'wz-modal-buttons'; const saveButton = UI.createButton(existingBlock ? 'Actualizar Bloque' : 'Insertar Bloque', 'wz-confirm', () => { errorDiv.style.display = 'none'; let validationFailed = false; const currentEntries = Array.from(entryList.querySelectorAll('.wz-bio-entry')).map((el, index) => { const dateText = el.querySelector('.wz-bio-date').value.trim(); const description = el.querySelector('.wz-bio-desc').value.trim(); const url = el.querySelector('.wz-bio-url').value.trim(); if (validationFailed) return null; if ((dateText || description || url)) { if (!dateText) { errorDiv.textContent = `Error en Entrada ${index + 1}: El campo de fecha es obligatorio.`; validationFailed = true; } else if (!description && !url) { errorDiv.textContent = `Error en Entrada ${index + 1}: El campo de descripción es obligatorio si no hay URL.`; validationFailed = true; } else if (!isValidBioDate(dateText)) { errorDiv.textContent = `Error en Entrada ${index + 1}: Formato de fecha inválido. Use 'DD de MES de AAAA', 'MES de AAAA' o 'AAAA'.`; validationFailed = true; } if (validationFailed) { errorDiv.style.display = 'block'; el.open = true; return null; } } return { dateText, url, description }; }); if (validationFailed) return; const validEntries = currentEntries.filter(e => e && e.dateText); if (validEntries.length === 0) { if (existingBlock) { UI.showModal("¿Eliminar bloque de biografía vacío?", "confirm", confirmed => { if (confirmed) { textarea.value = textarea.value.substring(0, existingBlock.startIndex) + textarea.value.substring(existingBlock.endIndex); UI.closeAllModals(); } }, true); } return; } let bioContent = Content.BIO_BLOCK.HEADER; validEntries.forEach(entry => { const prefix = getBioEntryPrefix(entry.dateText); let lineContent = entry.url ? `[${entry.dateText}](${entry.url})` : entry.dateText; if (entry.description) { let desc = entry.description; if (!/[.!?]$/.test(desc)) desc += '.'; lineContent += ` ${desc}`; } bioContent += `\n* ${prefix}${lineContent}`; }); if (existingBlock) { textarea.value = textarea.value.substring(0, existingBlock.startIndex) + bioContent + textarea.value.substring(existingBlock.endIndex); } else { const { textToInsert, cursorPosition } = ensureProperSpacing(textarea.value, bioContent, 'end'); textarea.value = textToInsert; textarea.selectionStart = textarea.selectionEnd = cursorPosition; } UI.closeAllModals(); }); buttonsDiv.append(UI.createButton('Cancelar', 'wz-cancel', UI.closeAllModals), saveButton); modalContent.appendChild(buttonsDiv); overlay.appendChild(modalContent); document.body.appendChild(overlay); UI.setupModalEscape(overlay, UI.closeAllModals); },
            applyForumDiscussionFormatting: function(textarea) { const titleInputElement = document.getElementById('reply-title'); if (!titleInputElement) { UI.showModal("Error: Campo de título #reply-title no encontrado.", 'alert'); return; } let postTitle = titleInputElement.value.trim(); if (!postTitle) { UI.showModal("Error: El título del post no puede estar vacío.", 'alert'); return; } const cleanedPostTitleForDisplay = postTitle.replace(/:[a-zA-Z0-9\_+-]+:/g, '').trim(); if (!cleanedPostTitleForDisplay) { UI.showModal("Error: Título (sin emojis) no puede estar vacío.", 'alert'); return; } const newGeneratedParams = { ...generateBodyContentAndTitleParams(cleanedPostTitleForDisplay), cleanedPostTitleForDisplay }; const forumBlockRegex = getForumBlockRegex(); const existingBlockMatch = textarea.value.match(forumBlockRegex); if (!existingBlockMatch) { const fullBlock = generateFullForumBlock(newGeneratedParams.bodyContentText, newGeneratedParams.urlEncodedTitleForNewTopic); const { textToInsert: finalContent, cursorPosition } = ensureProperSpacing(textarea.value, fullBlock, 'end', null); textarea.value = finalContent; textarea.selectionStart = textarea.selectionEnd = cursorPosition; textarea.focus(); textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); } else { const existingBlockText = existingBlockMatch[0]; const existingBlockInfo = { text: existingBlockText, startIndex: existingBlockMatch.index, endIndex: existingBlockMatch.index + existingBlockText.length }; const bodyMatch = existingBlockText.match(/pagina\sde\s([^"]*)/); const newTopicMatch = existingBlockText.match(/title=WAZO%20-%20([^&"]+)/); const currentParams = { bodyContent: (bodyMatch && bodyMatch[1]) || '', urlEncodedTitle: (newTopicMatch && newTopicMatch[1]) || '' }; if (decodeURIComponent(currentParams.bodyContent) === newGeneratedParams.bodyContentText && currentParams.urlEncodedTitle === newGeneratedParams.urlEncodedTitleForNewTopic) { UI.showModal("El bloque 'Foro de discusión' ya está actualizado.", 'alert'); } else { showForumUpdateConfirmModal(textarea, existingBlockInfo, newGeneratedParams, currentParams); } } },
            showFaqConfigModal: function(textarea) { UI.closeAllModals(); const existingBlock = parseExistingFaqBlock(textarea.value); const entries = existingBlock && existingBlock.entries.length > 0 ? existingBlock.entries : [{ question: '', answer: '' }]; const overlay = document.createElement('div'); overlay.className = 'wz-modal-overlay'; const modalContent = document.createElement('div'); modalContent.className = 'wz-modal-content'; modalContent.innerHTML = `<h3>Configurar Preguntas Frecuentes (FAQs)</h3><div class="wz-faq-modal-error" style="display:none;"></div><div class="wz-modal-scrollable-content"><div id="wz-faq-entry-list"></div></div>`; const errorDiv = modalContent.querySelector('.wz-faq-modal-error'); const entryListContainer = modalContent.querySelector('#wz-faq-entry-list'); entries.forEach((entry, index) => entryListContainer.appendChild(createFaqEntryElement(entry, index, entryListContainer))); const addBtn = UI.createButton('Añadir FAQ', 'wz-faq-add-entry-btn wz-confirm', () => { const newFaq = createFaqEntryElement(undefined, entryListContainer.children.length, entryListContainer); entryListContainer.appendChild(newFaq); newFaq.open = true; newFaq.querySelector('.wz-faq-question').focus(); }); modalContent.querySelector('.wz-modal-scrollable-content').appendChild(addBtn); const buttonsDiv = document.createElement('div'); buttonsDiv.className = 'wz-modal-buttons'; const saveButton = UI.createButton(existingBlock ? 'Actualizar Bloque' : 'Insertar Bloque', 'wz-confirm', () => { const faqEntries = Array.from(entryListContainer.querySelectorAll('.wz-faq-entry')).map(d => ({ question: d.querySelector('.wz-faq-question').value.trim(), answer: d.querySelector('.wz-faq-answer').value.trim() })).filter(e => e.question && e.answer); if (faqEntries.length === 0) { if (existingBlock) { UI.showModal("No hay FAQs. ¿Eliminar bloque existente?", "confirm", c => { if (c) { textarea.value = textarea.value.substring(0, existingBlock.startIndex) + textarea.value.substring(existingBlock.endIndex); UI.closeAllModals(); } }, true); } else { errorDiv.textContent = "No hay entradas para guardar."; errorDiv.style.display = 'block'; } return; } let faqContent = faqEntries.map(e => `**🔹 ${e.question}**\n\n${e.answer}`).join('\n\n'); let finalBlock = `---\n\n${Content.FAQ_BLOCK.HEADER}\n\n${faqContent}\n\n---`; if (existingBlock) { textarea.value = textarea.value.substring(0, existingBlock.startIndex) + finalBlock + textarea.value.substring(existingBlock.endIndex); } else { const { textToInsert, cursorPosition } = ensureProperSpacing(textarea.value, finalBlock, 'end'); textarea.value = textToInsert; textarea.selectionStart = textarea.selectionEnd = cursorPosition; } textarea.focus(); UI.closeAllModals(); }); buttonsDiv.append(UI.createButton('Cancelar', 'wz-cancel', UI.closeAllModals), saveButton); modalContent.appendChild(buttonsDiv); overlay.appendChild(modalContent); document.body.appendChild(overlay); UI.setupModalEscape(overlay, UI.closeAllModals); }
        };
    })();
    console.log('Wazeopedia Blocks Library v9.0.2.2 loaded.');
})();