BDO Navigator: Quick Access TOC

Interactive side Table of Contents for Black Desert. Provides instant navigation through patch note sections.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         BDO Navigator: Quick Access TOC
// @name:ru      BDO Navigator: Быстрое Содержание
// @name:ko      BDO 나침반: 빠른 목차 이동
// @name:tr      BDO Gezgini: Hızlı İçerik Menüsü
// @namespace    Sandr
// @version      1.1
// @description  Interactive side Table of Contents for Black Desert. Provides instant navigation through patch note sections.
// @description:ru Интерактивное боковое оглавление для Black Desert. Мгновенная навигация по разделам патчноута.
// @description:ko 검은사막 뉴스 페이지를 위한 인터랙티브 측면 목차. 패치 노트의 각 섹션으로 즉시 이동할 수 있습니다.
// @description:tr Black Desert için interaktif yan içerik tablosu. Yama notu bölümleri arasında anında gezinme sağlar.
// @description:de Interaktives seitliches Inhaltsverzeichnis für Black Desert. Ermöglicht die sofortige Navigation durch die Patch-Note-Abschnitte.
// @description:fr Table des matières latérale interactive pour Black Desert. Permet une navigation instantanée dans les sections des notes de patch.
// @description:es Tabla de contenidos lateral interactiva para Black Desert. Permite la navegación instantánea por las secciones de las notas del parche.
// @description:pt Tabela de conteúdos lateral interativa para o Black Desert. Permite a navegação instantânea pelas seções das notas de atualização.
// @description:th สารบัญด้านข้างแบบโต้ตอบสำหรับ Black Desert ช่วยให้เลื่อนไปยังส่วนต่างๆ ของแพตช์โน้ตได้อย่างรวดเร็ว
// @description:zh-tw 黑沙漠互動式側邊目錄。提供更新日誌章節 delivery 的快速導覽。
// @author       Sandr
// @match        https://*.playblackdesert.com/*/News/Detail*
// @match        https://*.playblackdesert.com/News/Notice/Detail*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=playblackdesert.com
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const init = () => {
        if (document.getElementById('bdo-quick-nav')) return;

        // 1. Ищем оригинальное оглавление (список ol)
        let originalToc = document.querySelector('.news_area ol') ||
                          document.querySelector('.contents_area ol') ||
                          document.querySelector('ol[data-maxwidthlimited="true"]');

        if (!originalToc) {
            // Запасной вариант: ищем ссылки-якоря, если ol не найден
            const anchors = document.querySelectorAll('.news_area a[href^="#"], .contents_area a[href^="#"]');
            if (anchors.length > 3) {
                originalToc = document.createElement('ol');
                anchors.forEach(a => {
                    const li = document.createElement('li');
                    li.appendChild(a.cloneNode(true));
                    originalToc.appendChild(li);
                });
            }
        }

        if (!originalToc) {
            setTimeout(init, 2000);
            return;
        }

        // 2. Ищем текст заголовка прямо на странице (h3 над оглавлением)
        let headerText = "Содержание"; // Значение по умолчанию
        const pageHeader = document.querySelector('h3[data-maxwidthlimited="true"]');
        if (pageHeader) {
            headerText = pageHeader.innerText.trim();
        }

        // 3. Стили (дизайн v1.5)
        const style = document.createElement('style');
        style.innerHTML = `
            #bdo-quick-nav {
                position: fixed !important;
                left: 0 !important;
                top: 15% !important;
                z-index: 999999 !important;
                display: flex !important;
                align-items: flex-start !important;
                font-family: "Noto Sans KR", sans-serif !important;
            }

            #bdo-nav-trigger {
                width: 32px; height: 54px;
                background: #1c1c22; color: #cebb9a;
                display: flex; align-items: center; justify-content: center;
                cursor: pointer; border: 1px solid #3d3d47; border-left: none;
                border-radius: 0 6px 6px 0; box-shadow: 4px 0 12px rgba(0,0,0,0.4);
                font-size: 20px;
            }

            #bdo-nav-panel {
                width: 300px; max-height: 75vh;
                background: #1c1c22; border: 1px solid #3d3d47;
                padding: 16px; overflow-y: auto;
                box-shadow: 8px 8px 24px rgba(0,0,0,0.6);
                display: none;
            }

            .bdo-nav-header {
                color: #cebb9a; font-weight: bold;
                border-bottom: 1px solid #3d3d47;
                margin-bottom: 12px; padding-bottom: 8px;
                text-transform: uppercase; font-size: 14px;
                letter-spacing: 0.5px;
            }

            #bdo-nav-panel ol { list-style: none !important; padding: 0 !important; margin: 0 !important; }
            #bdo-nav-panel li { margin-bottom: 10px !important; line-height: 1.3 !important; }
            #bdo-nav-panel a {
                color: #cfcfcf !important; text-decoration: none !important;
                font-size: 13.5px !important; transition: all 0.15s ease;
                display: block;
            }
            #bdo-nav-panel a:hover { color: #f0e1c5 !important; transform: translateX(4px); }
            #bdo-nav-panel ol ol { padding-left: 18px !important; margin-top: 8px !important; border-left: 1px solid #333; }

            #bdo-quick-nav:hover #bdo-nav-panel { display: block; }
            #bdo-quick-nav:hover #bdo-nav-trigger { display: none; }

            #bdo-nav-panel::-webkit-scrollbar { width: 4px; }
            #bdo-nav-panel::-webkit-scrollbar-thumb { background: #cebb9a; }
        `;
        document.head.appendChild(style);

        // 4. Сборка меню
        const container = document.createElement('div');
        container.id = 'bdo-quick-nav';
        const trigger = document.createElement('div');
        trigger.id = 'bdo-nav-trigger';
        trigger.innerHTML = '☰';
        const panel = document.createElement('div');
        panel.id = 'bdo-nav-panel';
        panel.innerHTML = `<div class="bdo-nav-header">${headerText}</div>`;

        const clonedContent = originalToc.cloneNode(true);
        panel.appendChild(clonedContent);

        container.appendChild(trigger);
        container.appendChild(panel);
        document.body.appendChild(container);

        // 5. Логика переходов
        panel.querySelectorAll('a').forEach(link => {
            link.onclick = (e) => {
                const href = link.getAttribute('href');
                if (href && href.includes('#')) {
                    e.preventDefault();
                    const targetId = decodeURIComponent(href.split('#')[1]);
                    const target = document.getElementById(targetId) ||
                                 document.querySelector(`a[name="${targetId}"]`) ||
                                 document.getElementsByName(targetId)[0];

                    if (target) {
                        window.scrollTo({
                            top: target.getBoundingClientRect().top + window.pageYOffset - 40,
                            behavior: 'auto'
                        });
                    }
                }
            };
        });
    };

    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }
})();