Drawaria Symbols Loader Menu

Load symbols from BarsikSymbols.json and allow inserting them into the chat with a click. Improved and draggable menu

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Drawaria Symbols Loader Menu
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Load symbols from BarsikSymbols.json and allow inserting them into the chat with a click. Improved and draggable menu
// @author       YouTubeDrawaria, Barsik Hacker
// @match        https://drawaria.online/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    const SYM_URL = "https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/BarsikSymbols.json";
    const PAGE_SIZE = 30; // Número de símbolos por página

    let allSymbols = []; // Almacena todos los símbolos cargados
    let currentPage = 0;
    let symLoaderBox; // Referencia al cuadro principal de la UI

    // Variables para la funcionalidad de arrastre
    let isDragging = false;
    let offsetX, offsetY;

    /**
     * Crea y añade la interfaz de usuario al DOM.
     * Incluye estilos CSS y la estructura HTML básica.
     */
    function createUI() {
        // --- Estilos CSS ---
        const style = document.createElement('style');
        style.textContent = `
            #symLoaderBox {
                position: fixed;
                top: 60px;
                right: 20px;
                background: #2b2b2b; /* Fondo oscuro */
                color: #e0e0e0; /* Texto claro */
                z-index: 9999; /* Asegura que esté por encima de otros elementos */
                border-radius: 10px;
                padding: 15px;
                width: 280px; /* Ancho ligeramente mayor */
                font-size: 15px;
                box-shadow: 0 4px 18px rgba(0, 0, 0, 0.4); /* Sombra suave */
                font-family: 'Arial', sans-serif;
                border: 1px solid #444; /* Borde sutil */
                cursor: grab; /* Cursor para indicar que es arrastrable */
            }

            #symLoaderHeader {
                display: flex;
                align-items: center;
                justify-content: space-between;
                margin-bottom: 10px;
                padding-bottom: 5px;
                border-bottom: 1px solid #3a3a3a; /* Separador */
                cursor: grab; /* Cursor para indicar que el header es arrastrable */
            }
            #symLoaderHeader span {
                font-size: 18px;
                font-weight: bold;
                color: #fff;
            }

            #symLoaderBox button {
                margin: 0 2px;
                padding: 5px 9px;
                font-size: 17px;
                vertical-align: middle;
                border-radius: 5px;
                border: none;
                background: #444;
                color: #fff;
                cursor: pointer;
                transition: background 0.2s ease, transform 0.1s ease; /* Transiciones suaves */
            }
            #symLoaderBox button:hover {
                background: #666;
                transform: translateY(-1px); /* Efecto de "levantar" */
            }
            #symLoaderBox button:active {
                transform: translateY(0);
            }

            #symLoaderDL {
                background: #3a7bd5; /* Color distintivo para descargar */
            }
            #symLoaderDL:hover {
                background: #2a6bc5;
            }

            #symLoaderClose {
                background: #d32f2f; /* Rojo para cerrar */
            }
            #symLoaderClose:hover {
                background: #c31f1f;
            }

            #symLoaderSymbols {
                max-height: 280px; /* Altura máxima para el scroll */
                overflow-y: auto;
                word-break: break-all; /* Rompe palabras largas */
                white-space: normal;
                padding-right: 5px; /* Espacio para la barra de desplazamiento */
                margin-bottom: 10px;
            }

            /* Estilos de la barra de desplazamiento (para navegadores Webkit como Chrome, Safari) */
            #symLoaderSymbols::-webkit-scrollbar {
                width: 8px;
            }
            #symLoaderSymbols::-webkit-scrollbar-track {
                background: #333;
                border-radius: 10px;
            }
            #symLoaderSymbols::-webkit-scrollbar-thumb {
                background: #666;
                border-radius: 10px;
            }
            #symLoaderSymbols::-webkit-scrollbar-thumb:hover {
                background: #888;
            }

            .symBtn { /* Estilo para los botones de símbolos individuales */
                display: inline-block;
                background: #555;
                color: #fff;
                padding: 4px 8px;
                margin: 3px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 16px;
                transition: background 0.15s ease, transform 0.05s ease;
                user-select: none; /* Previene la selección de texto */
            }
            .symBtn:hover {
                background: #777;
                transform: scale(1.02);
            }
            .symBtn:active {
                transform: scale(1.0);
            }

            #symLoaderPager {
                display: flex;
                justify-content: center;
                align-items: center;
                margin-top: 5px;
                padding-top: 5px;
                border-top: 1px solid #3a3a3a; /* Separador */
            }
            #symLoaderPager .pager-button { /* Clase para los botones de paginación */
                background: #007bff;
                color: #fff;
                padding: 3px 8px;
                margin: 0 5px;
                font-size: 15px;
                border-radius: 4px;
                transition: background 0.2s ease;
            }
            #symLoaderPager .pager-button:hover {
                background: #0056b3;
            }
            #symLoaderPager .pager-button:disabled {
                background: #555;
                cursor: not-allowed;
            }
            #symLoaderPager span {
                font-size: 15px;
                color: #bbb;
            }
        `;
        document.head.appendChild(style);

        // --- Estructura HTML ---
        symLoaderBox = document.createElement('div');
        symLoaderBox.id = 'symLoaderBox';
        symLoaderBox.innerHTML = `
            <div id="symLoaderHeader">
                <span>Drawaria Symbols</span>
                <div>
                    <button id="symLoaderDL">💾</button>
                    <button id="symLoaderClose">❌</button>
                </div>
            </div>
            <div id="symLoaderSymbols">
                Cargando símbolos...
            </div>
            <div id="symLoaderPager">
                <!-- Los botones de paginación se insertarán aquí -->
            </div>
        `;
        document.body.appendChild(symLoaderBox);

        // --- Event Listeners ---
        document.getElementById('symLoaderClose').onclick = () => symLoaderBox.remove();
        document.getElementById('symLoaderDL').onclick = downloadJSON;

        // Delegación de eventos para los botones de símbolos (eficiente para muchos botones)
        document.getElementById('symLoaderBox').addEventListener('click', e => {
            if (e.target.classList.contains('symBtn')) {
                insertToChat(e.target.dataset.symbol);
            }
        });

        // --- Draggable functionality ---
        const symLoaderHeader = document.getElementById('symLoaderHeader');

        symLoaderHeader.addEventListener('mousedown', (e) => {
            isDragging = true;
            // Calcular el desplazamiento del cursor dentro del elemento
            offsetX = e.clientX - symLoaderBox.getBoundingClientRect().left;
            offsetY = e.clientY - symLoaderBox.getBoundingClientRect().top;
            symLoaderBox.style.cursor = 'grabbing'; // Cambiar cursor al arrastrar
            // Prevenir la selección de texto durante el arrastre
            symLoaderBox.style.userSelect = 'none';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;

            // Calcular la nueva posición del elemento
            let newLeft = e.clientX - offsetX;
            let newTop = e.clientY - offsetY;

            // Opcional: Limitar el arrastre dentro de la ventana
            const maxX = window.innerWidth - symLoaderBox.offsetWidth;
            const maxY = window.innerHeight - symLoaderBox.offsetHeight;

            newLeft = Math.max(0, Math.min(newLeft, maxX));
            newTop = Math.max(0, Math.min(newTop, maxY));

            symLoaderBox.style.left = `${newLeft}px`;
            symLoaderBox.style.top = `${newTop}px`;
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            symLoaderBox.style.cursor = 'grab'; // Restaurar cursor
            symLoaderBox.style.userSelect = 'auto'; // Habilitar selección de texto
        });
    }

    /**
     * Rellena el contenedor de símbolos con los símbolos de la página actual.
     * Siempre usa `allSymbols` ya que no hay filtro.
     * @param {number} page - El número de página a mostrar (0-indexado).
     */
    function populateSymbols(page = 0) {
        if (!allSymbols || allSymbols.length === 0) {
            fillSymbols('No hay símbolos disponibles.');
            updatePager(0, 0, 0); // Actualiza la paginación a "sin páginas"
            return;
        }

        currentPage = page;
        const wrap = document.getElementById('symLoaderSymbols');
        wrap.innerHTML = ''; // Limpia los símbolos anteriores

        let start = page * PAGE_SIZE;
        let end = Math.min(start + PAGE_SIZE, allSymbols.length);

        const fragment = document.createDocumentFragment(); // Para mejor rendimiento al añadir muchos elementos
        for (let i = start; i < end; i++) {
            const symbol = allSymbols[i];
            const button = document.createElement('button');
            button.classList.add('symBtn');
            button.dataset.symbol = symbol; // Almacena el símbolo en un atributo de datos
            button.textContent = symbol;
            fragment.appendChild(button);
        }
        wrap.appendChild(fragment);

        updatePager(page, allSymbols.length, PAGE_SIZE);
    }

    /**
     * Actualiza los botones y la información de la paginación.
     * @param {number} currentPage - La página actual (0-indexada).
     * @param {number} totalSymbols - El número total de símbolos disponibles.
     * @param {number} pageSize - El número de símbolos por página.
     */
    function updatePager(currentPage, totalSymbols, pageSize) {
        const pager = document.getElementById('symLoaderPager');
        pager.innerHTML = ''; // Limpia la paginación existente

        if (totalSymbols <= pageSize) {
            return; // No se necesita paginación si hay una sola página o menos
        }

        const totalPages = Math.ceil(totalSymbols / pageSize);

        // Botón "Anterior"
        const prevButton = document.createElement('button');
        prevButton.classList.add('pager-button');
        prevButton.textContent = '<';
        prevButton.disabled = currentPage === 0; // Deshabilitar si es la primera página
        prevButton.onclick = () => populateSymbols(currentPage - 1);
        pager.appendChild(prevButton);

        // Información de la página (ej: "1/5")
        const pageInfo = document.createElement('span');
        pageInfo.textContent = `${currentPage + 1}/${totalPages}`;
        pager.appendChild(pageInfo);

        // Botón "Siguiente"
        const nextButton = document.createElement('button');
        nextButton.classList.add('pager-button');
        nextButton.textContent = '>';
        nextButton.disabled = currentPage >= totalPages - 1; // Deshabilitar si es la última página
        nextButton.onclick = () => populateSymbols(currentPage + 1);
        pager.appendChild(nextButton);
    }

    /**
     * Rellena el contenedor de símbolos con un mensaje de estado.
     * @param {string} msg - El mensaje a mostrar.
     */
    function fillSymbols(msg) {
        document.getElementById('symLoaderSymbols').textContent = msg;
    }

    /**
     * Descarga los símbolos cargados como un archivo JSON.
     */
    function downloadJSON() {
        const a = document.createElement('a');
        // Convierte el array a JSON con indentación para que sea legible
        a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(allSymbols, null, 2));
        a.download = 'BarsikSymbols.json';
        a.click();
    }

    /**
     * Inserta el texto dado en el campo de entrada del chat.
     * Se actualizó el selector para el chatbox de Drawaria.
     * @param {string} txt - El texto a insertar.
     */
    function insertToChat(txt) {
        const chatInput = document.getElementById('chatbox_textinput'); // Selector actualizado
        if (!chatInput) {
            alert('No se ha encontrado el input de chat con ID "chatbox_textinput".');
            return;
        }
        chatInput.value = txt;
        // Dispara un evento 'input' para asegurar que frameworks como React/Vue detecten el cambio
        chatInput.dispatchEvent(new Event('input', { bubbles: true }));
        // Opcional: enviar el mensaje (descomenta la siguiente línea, ¡bajo tu responsabilidad!)
        // document.querySelector('.chat__message-form button')?.click();
    }

    /**
     * Carga los símbolos desde la URL especificada.
     */
    async function loadSymbols() {
        fillSymbols("Cargando símbolos..."); // Mostrar mensaje de carga
        try {
            const response = await fetch(SYM_URL);
            if (!response.ok) {
                throw new Error(`Error HTTP! estado: ${response.status}`);
            }
            const obj = await response.json();
            // Soporta si el JSON es un array directamente o un objeto con una propiedad 'symbols'
            allSymbols = Array.isArray(obj) ? obj : obj.symbols ?? [];

            if (allSymbols.length === 0) {
                fillSymbols("No se encontraron símbolos en el archivo JSON o el formato es incorrecto.");
            } else {
                populateSymbols(0); // Carga la página inicial de símbolos
            }
        } catch (e) {
            console.error("Error al cargar símbolos:", e);
            fillSymbols(`Error al cargar símbolos: ${e.message}. Por favor, intenta recargar la página.`);
        }
    }

    // Inicializa la interfaz de usuario y carga los símbolos al iniciar el script
    createUI();
    loadSymbols();
})();