魂+收藏夹

支持层级目录、删除、拖动排序、折叠状态记忆和目录重命名

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 or Violentmonkey 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.

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

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

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

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

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

// ==UserScript==
// @name         魂+收藏夹
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  支持层级目录、删除、拖动排序、折叠状态记忆和目录重命名
// @author       annet
// @match        https://bbs.imoutolove.me/read.php?*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // 添加样式
    GM_addStyle(`
        .my-bookmark-btn {
            color: #444;
            cursor: pointer;
            margin: 0 5px;
        }
        .my-bookmark-btn:hover {
            text-decoration: underline;
        }
        .bookmark-list {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 10px;
            max-height: 80vh;
            overflow-y: auto;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            z-index: 9998;
            display: none;
            width: 450px;
        }
        .bookmark-category {
            font-weight: bold;
            margin: 15px 0 5px 0;
            color: #333;
            padding: 5px;
            background: #f5f5f5;
            border-radius: 3px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: pointer;
        }
        .bookmark-category:hover {
            background: #eaeaea;
        }
        .bookmark-items {
            margin-left: 15px;
            transition: all 0.3s ease;
        }
        .bookmark-item {
            margin-bottom: 5px;
            padding: 8px;
            border-radius: 3px;
            background: #f9f9f9;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: move;
        }
        .bookmark-item:hover {
            background: #f0f0f0;
        }
        .bookmark-title {
            color: #4a6ea9;
            text-decoration: none;
            flex-grow: 1;
        }
        .bookmark-title:hover {
            text-decoration: underline;
        }
        .delete-btn {
            color: #ff6b6b;
            cursor: pointer;
            margin-left: 10px;
            font-weight: bold;
            padding: 0 5px;
        }
        .close-button {
            float: right;
            cursor: pointer;
            font-weight: bold;
            margin-bottom: 10px;
        }
        .drag-over {
            border: 2px dashed #4a6ea9;
            background: #e6f0ff !important;
        }
        .collapse-icon {
            margin-left: 10px;
            font-size: 12px;
        }
        .category-edit {
            border: 1px solid #ddd;
            padding: 3px;
            width: 80%;
            font-weight: bold;
        }
    `);

    // 创建收藏列表容器
    const bookmarkList = document.createElement('div');
    bookmarkList.className = 'bookmark-list';
    document.body.appendChild(bookmarkList);

    // 查找目标位置插入按钮
    const targetContainer = document.querySelector('.fr.w');
    if (!targetContainer) return;

    // 创建显示目录按钮
    const showButton = document.createElement('a');
    showButton.className = 'fn my-bookmark-btn';
    showButton.textContent = '显示目录';
    showButton.style.cursor = 'pointer';

    // 创建收藏按钮
    const saveButton = document.createElement('a');
    saveButton.className = 'fn my-bookmark-btn';
    saveButton.textContent = '收藏';
    saveButton.style.cursor = 'pointer';

    // 获取所有现有的链接
    const existingLinks = targetContainer.querySelectorAll('a');
    const browserBookmarkLink = Array.from(existingLinks).find(a => a.onclick && a.onclick.toString().includes('Addtoie'));
    const printLink = Array.from(existingLinks).find(a => a.href.includes('simple/index.php'));

    // 清空容器并重新构建
    targetContainer.innerHTML = '';

    // 添加分隔符和按钮
    targetContainer.appendChild(document.createTextNode(' | '));
    targetContainer.appendChild(showButton);
    targetContainer.appendChild(document.createTextNode(' | '));
    targetContainer.appendChild(saveButton);
    targetContainer.appendChild(document.createTextNode(' | '));
    if (browserBookmarkLink) targetContainer.appendChild(browserBookmarkLink);
    targetContainer.appendChild(document.createTextNode(' | '));
    if (printLink) targetContainer.appendChild(printLink);

    // 其余功能代码保持不变...
    // [这里保留之前的所有功能代码]

    // 获取折叠状态
    function getCollapseState() {
        return GM_getValue('collapseState', {});
    }

    // 设置折叠状态
    function setCollapseState(path, isCollapsed) {
        const collapseState = getCollapseState();
        collapseState[path] = isCollapsed;
        GM_setValue('collapseState', collapseState);
    }

    // 获取当前页面的目录和标题
    function getPageInfo() {
        let categories = [];
        let title = document.title;
        const url = window.location.href;

        // 从面包屑导航获取目录层级
        const breadcrumbs = document.querySelector('#breadcrumbs');
        if (breadcrumbs) {
            // 获取所有.crumbs-item.gray3元素作为目录层级
            const categoryItems = breadcrumbs.querySelectorAll('.crumbs-item.gray3');
            categoryItems.forEach(item => {
                categories.push(item.textContent.trim());
            });

            // 获取当前页面标题(最后一个strong或a元素)
            const lastTitle = breadcrumbs.querySelector('strong a, .current a');
            if (lastTitle) {
                title = lastTitle.textContent.trim();
            }
        }

        // 如果没有从面包屑获取到目录,尝试其他方法
        if (categories.length === 0) {
            categories = ['未分类'];
        }

        return { categories, title, url };
    }

    // 保存书签
    saveButton.addEventListener('click', () => {
        const { categories, title, url } = getPageInfo();
        let bookmarks = GM_getValue('bookmarks', {});

        // 构建目录层级
        let currentLevel = bookmarks;
        for (let i = 0; i < categories.length; i++) {
            const category = categories[i];
            if (!currentLevel[category]) {
                currentLevel[category] = i === categories.length - 1 ? [] : {};
            }
            currentLevel = currentLevel[category];
        }

        // 检查是否已经收藏过
        if (Array.isArray(currentLevel)) {
            const alreadySaved = currentLevel.some(item => item.url === url);
            if (alreadySaved) {
                return;
            }

            currentLevel.push({ title, url });
            GM_setValue('bookmarks', bookmarks);
        }
    });

    // 显示/隐藏书签列表
    showButton.addEventListener('click', () => {
        if (bookmarkList.style.display === 'block') {
            bookmarkList.style.display = 'none';
        } else {
            displayBookmarks();
            bookmarkList.style.display = 'block';
        }
    });

    // 递归渲染书签列表
    function renderBookmarks(data, parentElement, path = []) {
        for (const key in data) {
            if (Array.isArray(data[key])) {
                // 这是分类下的书签列表
                if (data[key].length > 0) {
                    const fullPath = path.join('|') + (path.length ? '|' : '') + key;
                    const collapseState = getCollapseState();
                    const isCollapsed = collapseState[fullPath] || false;

                    const categoryDiv = document.createElement('div');
                    categoryDiv.className = 'bookmark-category';
                    categoryDiv.dataset.path = fullPath;

                    const categoryName = document.createElement('span');
                    categoryName.textContent = key;
                    categoryDiv.appendChild(categoryName);

                    const collapseIcon = document.createElement('span');
                    collapseIcon.className = 'collapse-icon';
                    collapseIcon.textContent = isCollapsed ? '▶' : '▼';
                    categoryDiv.appendChild(collapseIcon);

                    parentElement.appendChild(categoryDiv);

                    const itemsContainer = document.createElement('div');
                    itemsContainer.className = 'bookmark-items';
                    itemsContainer.style.display = isCollapsed ? 'none' : 'block';
                    parentElement.appendChild(itemsContainer);

                    // 添加折叠/展开功能
                    categoryDiv.addEventListener('click', function(e) {
                        if (e.target.classList.contains('delete-btn')) return;

                        if (e.target === this || e.target === categoryName || e.target === collapseIcon) {
                            const newState = itemsContainer.style.display === 'none';
                            itemsContainer.style.display = newState ? 'block' : 'none';
                            collapseIcon.textContent = newState ? '▼' : '▶';
                            setCollapseState(fullPath, !newState);
                        }
                    });

                    // 添加双击重命名功能
                    categoryDiv.addEventListener('dblclick', function(e) {
                        if (e.target.classList.contains('delete-btn')) return;

                        const oldName = categoryName.textContent;
                        const input = document.createElement('input');
                        input.type = 'text';
                        input.className = 'category-edit';
                        input.value = oldName;

                        categoryName.replaceWith(input);
                        input.focus();

                        const handleRename = () => {
                            const newName = input.value.trim();
                            if (newName && newName !== oldName) {
                                // 更新数据
                                let bookmarks = GM_getValue('bookmarks');
                                let currentLevel = bookmarks;

                                // 导航到父级
                                for (let i = 0; i < path.length; i++) {
                                    currentLevel = currentLevel[path[i]];
                                }

                                // 重命名
                                currentLevel[newName] = currentLevel[oldName];
                                delete currentLevel[oldName];

                                // 更新折叠状态
                                const collapseState = getCollapseState();
                                if (collapseState[fullPath] !== undefined) {
                                    collapseState[fullPath.replace(oldName, newName)] = collapseState[fullPath];
                                    delete collapseState[fullPath];
                                    GM_setValue('collapseState', collapseState);
                                }

                                GM_setValue('bookmarks', bookmarks);
                                displayBookmarks();
                            } else {
                                input.replaceWith(categoryName);
                            }
                        };

                        input.addEventListener('blur', handleRename);
                        input.addEventListener('keypress', function(e) {
                            if (e.key === 'Enter') {
                                handleRename();
                            }
                        });
                    });

                    data[key].forEach((bookmark, index) => {
                        const itemDiv = document.createElement('div');
                        itemDiv.className = 'bookmark-item';
                        itemDiv.draggable = true;
                        itemDiv.dataset.path = [...path, key, index].join('|');

                        const titleLink = document.createElement('a');
                        titleLink.className = 'bookmark-title';
                        titleLink.textContent = bookmark.title;
                        titleLink.href = bookmark.url;
                        titleLink.target = '_blank';
                        itemDiv.appendChild(titleLink);

                        const deleteBtn = document.createElement('span');
                        deleteBtn.className = 'delete-btn';
                        deleteBtn.textContent = '×';
                        deleteBtn.title = '删除';
                        deleteBtn.addEventListener('click', (e) => {
                            e.stopPropagation();
                            // 获取完整路径
                            const fullPath = itemDiv.dataset.path.split('|');
                            let bookmarks = GM_getValue('bookmarks');
                            let currentLevel = bookmarks;

                            // 导航到父级
                            for (let i = 0; i < fullPath.length - 1; i++) {
                                currentLevel = currentLevel[fullPath[i]];
                            }

                            // 删除项目
                            const indexToDelete = parseInt(fullPath[fullPath.length - 1]);
                            currentLevel.splice(indexToDelete, 1);

                            GM_setValue('bookmarks', bookmarks);
                            displayBookmarks();
                        });
                        itemDiv.appendChild(deleteBtn);

                        // 拖动相关事件
                        itemDiv.addEventListener('dragstart', handleDragStart);
                        itemDiv.addEventListener('dragover', handleDragOver);
                        itemDiv.addEventListener('dragleave', handleDragLeave);
                        itemDiv.addEventListener('drop', handleDrop);
                        itemDiv.addEventListener('dragend', handleDragEnd);

                        itemsContainer.appendChild(itemDiv);
                    });
                }
            } else {
                // 这是子分类
                renderBookmarks(data[key], parentElement, [...path, key]);
            }
        }
    }

    // 显示书签列表
    function displayBookmarks() {
        const bookmarks = GM_getValue('bookmarks', {});
        bookmarkList.innerHTML = '<div class="close-button" id="closeBookmarkList">×</div><h3>我的收藏夹</h3>';

        if (Object.keys(bookmarks).length === 0) {
            bookmarkList.innerHTML += '<p>暂无收藏内容</p>';
            return;
        }

        renderBookmarks(bookmarks, bookmarkList);

        // 添加关闭按钮事件
        document.getElementById('closeBookmarkList').addEventListener('click', () => {
            bookmarkList.style.display = 'none';
        });
    }

    // 拖动相关变量
    let draggedItem = null;
    let draggedPath = null;

    // 拖动事件处理
    function handleDragStart(e) {
        draggedItem = this;
        draggedPath = this.dataset.path.split('|');
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('text/html', this.innerHTML);
        setTimeout(() => this.classList.add('drag-over'), 0);
    }

    function handleDragOver(e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';
        this.classList.add('drag-over');
        return false;
    }

    function handleDragLeave() {
        this.classList.remove('drag-over');
    }

    function handleDrop(e) {
        e.stopPropagation();
        e.preventDefault();
        this.classList.remove('drag-over');

        if (draggedItem !== this) {
            const targetPath = this.dataset.path.split('|');

            // 检查是否在同一分类下
            if (draggedPath.slice(0, -1).join() === targetPath.slice(0, -1).join()) {
                let bookmarks = GM_getValue('bookmarks');
                let currentLevel = bookmarks;

                // 导航到父级分类
                for (let i = 0; i < draggedPath.length - 1; i++) {
                    currentLevel = currentLevel[draggedPath[i]];
                }

                // 获取原始索引和目标索引
                const draggedIndex = parseInt(draggedPath[draggedPath.length - 1]);
                const targetIndex = parseInt(targetPath[targetPath.length - 1]);

                // 移动项目
                const [movedItem] = currentLevel.splice(draggedIndex, 1);
                currentLevel.splice(targetIndex, 0, movedItem);

                // 保存更新
                GM_setValue('bookmarks', bookmarks);
                displayBookmarks();
            }
        }
        return false;
    }

    function handleDragEnd() {
        this.classList.remove('drag-over');
    }

    // 点击页面其他地方关闭书签列表
    document.addEventListener('click', (e) => {
        if (!bookmarkList.contains(e.target) &&
            e.target !== showButton &&
            e.target !== saveButton) {
            bookmarkList.style.display = 'none';
        }
    });
})();