BangumiLazyPreviewLinkForWiki

Lazy load links and show their titles

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);
})();