BangumiLazyPreviewLinkForWiki

Lazy load links and show their titles

// ==UserScript==
// @name         BangumiLazyPreviewLinkForWiki
// @namespace    https://github.com/Adachi-Git/BangumiLazyPreviewLink
// @version      0.8
// @description  Lazy load links and show their titles
// @author       Jirehlov (Original Author), Adachi (Current Author)
// @include      /^https?://(bangumi\.tv|bgm\.tv|chii\.in)/.*
// @grant        none
// @license      MIT
// @require      https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js
// ==/UserScript==

(function () {
    'use strict';

    // 初始化 localForage
    localforage.config({
        driver: localforage.INDEXEDDB, // 使用 IndexedDB 存储
        name: 'localforage', // 指定数据库名称
        version: 1.0, // 数据库版本
        storeName: 'keyvaluepairs' // 存储链接的对象存储空间名称
    });

    // 删除可视区域内的零宽空格字符
    function removeZeroWidthSpacesInView() {
        document.querySelectorAll('*').forEach(element => {
            const rect = element.getBoundingClientRect();
            // 检查元素是否在可视区域内
            if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
                element.childNodes.forEach(node => {
                    if (node.nodeType === Node.TEXT_NODE) {
                        node.textContent = node.textContent.replace(/\u200B/g, ''); // 替换零宽空格字符
                    }
                });
            }
        });
    }

    // 替换链接文本为链接指向页面的标题
    async function replaceLinkText(link) {
        try {
            let linkURL = link.href;
            if (window.location.href.includes('bangumi.tv')) {
                linkURL = linkURL.replace('bgm.tv', 'bangumi.tv');
            } else if (window.location.href.includes('chii.in')) {
                linkURL = linkURL.replace(/bangumi\.tv|bgm\.tv/, 'chii.in');
            }

            if (link.textContent === link.href) {
                console.log(`Processing link: ${linkURL}`);
                const cachedTitle = await localforage.getItem(linkURL);
                console.log(`Cached title for ${linkURL}: ${cachedTitle}`);
                if (cachedTitle) {
                    link.textContent = cachedTitle + ',';
                } else {
                    console.log('Fetching data from network...');
                    const response = await fetch(linkURL);
                    const data = await response.text();
                    const parser = new DOMParser();
                    const htmlDoc = parser.parseFromString(data, 'text/html');
                    const title = htmlDoc.querySelector('h1.nameSingle a');
                    let titleText = title ? title.textContent : '';
                    if (link.href.includes('subject') || link.href.includes('ep')) {
                        const chineseName = title ? title.getAttribute('title') : '';
                        if (chineseName) {
                            if (titleText) {
                                titleText += ' | ' + chineseName;
                            } else {
                                titleText = chineseName;
                            }
                        }
                    }
                    if (link.href.includes('ep')) {
                        const epTitle = htmlDoc.querySelector('h2.title');
                        if (epTitle) {
                            epTitle.querySelectorAll('small').forEach(small => small.remove());
                            const epTitleText = epTitle.textContent;
                            if (epTitleText) {
                                if (titleText) {
                                    titleText += ' | ' + epTitleText;
                                } else {
                                    titleText = epTitleText;
                                }
                            }
                        }
                    }
                    if (titleText) {
                        link.textContent = titleText + ',';
                        console.log(`Title for ${linkURL} retrieved from network: ${titleText}`);
                        await localforage.setItem(linkURL, titleText);
                        console.log(`Cached title for ${linkURL}`);
                    }
                }
            }
        } catch (error) {
            console.error('Error in replaceLinkText:', error);
        }
    }

    // 获取页面上的所有链接
    const allLinks = document.querySelectorAll('a[href^="https://bgm.tv/subject/"], a[href^="https://chii.in/subject/"], a[href^="https://bangumi.tv/subject/"], a[href^="https://bgm.tv/ep/"], a[href^="https://chii.in/ep/"], a[href^="https://bangumi.tv/ep/"], a[href^="https://bgm.tv/character/"], a[href^="https://chii.in/character/"], a[href^="https://bangumi.tv/character/"], a[href^="https://bgm.tv/person/"], a[href^="https://chii.in/person/"], a[href^="https://bangumi.tv/person/"]');

    // 懒加载链接的函数
    function lazyLoadLinks() {
        allLinks.forEach(link => {
            const rect = link.getBoundingClientRect();
            if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
                replaceLinkText(link);
            }
        });
    }

    // 在滚动事件中触发懒加载
    window.addEventListener('scroll', () => {
        // 当页面滚动停止一段时间后再执行懒加载
        clearTimeout(window.timer);
        window.timer = setTimeout(() => {
            lazyLoadLinks();
        }, 200); // 等待200毫秒
    });

    // 监听文本选中事件
    let selectionTimeout;
    document.addEventListener('selectionchange', () => {
        clearTimeout(selectionTimeout);
        selectionTimeout = setTimeout(() => {
            const selection = window.getSelection();
            let selectedText = selection.toString().trim();
            selectedText = selectedText.replace(/,$/, ''); // 去除选定文本末尾的逗号
            if (selectedText) {
                const selectedWords = selectedText.split(/,\s*/); // 使用逗号和可能的空格进行分词
                const selectedIDs = [];
                allLinks.forEach(link => {
                    const linkText = link.textContent.trim().replace(/,$/, ''); // 去除链接文本末尾的逗号
                    if (selectedWords.some(word => linkText.includes(word))) {
                        const id = link.href.match(/\d+/)[0];
                        selectedIDs.push(id);
                    }
                });
                if (selectedIDs.length > 0) {
                    showFloatingDiv(selectedIDs);
                }
            } else {
                hideFloatingDiv();
            }
        }, 500); // 等待500毫秒
    });

    // 创建浮动窗口元素
    const floatingDiv = document.createElement('div');
    floatingDiv.style.position = 'fixed';
    floatingDiv.style.top = '50px';
    floatingDiv.style.left = '50px';
    floatingDiv.style.padding = '10px';
    floatingDiv.style.background = '#fff';
    floatingDiv.style.border = '1px solid #ccc';
    floatingDiv.style.borderRadius = '5px';
    floatingDiv.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.1)';
    floatingDiv.style.zIndex = '9999';
    floatingDiv.style.display = 'none';
    document.body.appendChild(floatingDiv);

    // 显示浮动窗口
    function showFloatingDiv(ids) {
        floatingDiv.innerHTML = ''; // 清空浮动窗口内容
        const uniqueIDs = [...new Set(ids)]; // 使用 Set 来获取唯一的 ID
        uniqueIDs.forEach(id => {
            const itemDiv = document.createElement('div');
            itemDiv.textContent = id + ',';
            floatingDiv.appendChild(itemDiv);
        });
        floatingDiv.style.display = 'block';

        // 添加点击事件监听器
        floatingDiv.addEventListener('click', () => {
            copyAllToClipboard(uniqueIDs);
        });
    }

    // 复制全部文本到剪贴板,并隐藏浮动窗口
    function copyAllToClipboard(ids) {
        const clipboardText = ids.join(',');
        navigator.clipboard.writeText(clipboardText)
            .then(() => {
            console.log('Copied to clipboard:', clipboardText);
            hideFloatingDiv(); // 复制完成后隐藏悬浮框
        })
            .catch(err => console.error('Failed to copy to clipboard:', err));
    }

    // 隐藏浮动窗口
    function hideFloatingDiv() {
        floatingDiv.style.display = 'none';
    }

    // 页面加载完成时立即执行一次懒加载
    window.addEventListener('DOMContentLoaded', lazyLoadLinks);

    // 页面完全加载后再执行一次删除零宽空格字符
    window.addEventListener('load', removeZeroWidthSpacesInView);
})();