Wazeopedia Blocks Library

Biblioteca de lógica para bloques de contenido de Wazeopedia (Título, Bio, FAQ, etc.).

当前为 2025-06-08 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/538615/1604134/Wazeopedia%20Blocks%20Library.js

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Wazeopedia Blocks Library
// @namespace    http://tampermonkey.net/
// @version      7.0.4
// @description  Biblioteca de lógica para bloques de contenido de Wazeopedia (Título, Bio, FAQ, etc.).
// @author       Annthizze
// @require      https://update.greasyfork.org/scripts/538610/Wazeopedia%20Core%20UI%20Library.js
// @require      https://update.greasyfork.org/scripts/538744/Wazeopedia%20Content%20Library.js
// @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 PRIVADAS PRIMERO ---

        const TITLE_STATUS_OPTIONS = Content.TITLE_BLOCK.STATUS_OPTIONS;
        const FORUM_BLOCK_REGEX_STR = `(?:^|\\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>`;
        const FAQ_BLOCK_REGEX = /(?:^|\n)---\s*\n+# \[wzh=1\]Preguntas Frecuentes\[\/wzh\]\s*\n+([\s\S]*?)\n+---\s*(?:\n|$)/;
        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 = /^(\d{4})$/;

        function isValidBioDate(dateText) { const text = dateText.trim(); return dateRegexDayMonthYear.test(text) || dateRegexMonthYear.test(text) || dateRegexYear.test(text); }
        function getBioEntryPrefix(dateText) { const text = (dateText || "").trim().toLowerCase(); if (dateRegexDayMonthYear.test(text)) return "El "; if (dateRegexMonthYear.test(text)) return "En "; if (dateRegexYear.test(text)) return "En el año "; return ""; }
        function 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 }; }
        function 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 TITLE_STATUS_OPTIONS) { if (content.includes(TITLE_STATUS_OPTIONS[key].text.split('***')[1])) { statusKey = key; if (TITLE_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 }; }
        function 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 }; }
        function 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) { foundMatch = dmyMatch[1]; } else if (myMatch) { foundMatch = myMatch[1]; } else if (yMatch) { foundMatch = yMatch[1]; } if (foundMatch && core.startsWith(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 }; }
        function 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; }
        function 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}`; }); }
        function parseExistingFaqBlock(editorText) { 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 }; }
        function 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(); previewElement.innerHTML = `<strong>🔹 ${question || 'Pregunta'}</strong><br>${answer || '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; }
        function 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) }; }
        function 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)}`; }

        // --- API PÚBLICA ---
        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(TITLE_STATUS_OPTIONS).map(k => `<option value="${k}" ${initial.statusKey === k ? 'selected' : ''}>${TITLE_STATUS_OPTIONS[k].label}</option>`).join('')}</select><div id="wz-title-forum-url-section" style="display: ${TITLE_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 = TITLE_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 (TITLE_STATUS_OPTIONS[statusKey]?.requiresUrl && !forumUrl) { errorDiv.textContent = 'URL de foro requerida.'; errorDiv.style.display = 'block'; return; } const statusText = TITLE_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); 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); 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 || !description) { errorDiv.textContent = `Error en Entrada ${index + 1}: La fecha y la descripción son obligatorias.`; 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); },
            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 = new RegExp(FORUM_BLOCK_REGEX_STR, 'm'); 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 de (\[.*?\]\(.*?\))/); const newTopicMatch = existingBlockText.match(/title=WAZO%20-%20([^&"]+)/); const currentParams = { bodyContent: (bodyMatch && bodyMatch[1]) || '', urlEncodedTitle: (newTopicMatch && newTopicMatch[1]) || '' }; if (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 : [{ 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); }
        };
    })();
    console.log('Wazeopedia Blocks Library 7.0.4 loaded.');
})();