Greasy Fork

来自缓存

Greasy Fork is available in English.

AcWing 错题本管理

在 AcWing 网站上添加错题本管理功能。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         AcWing 错题本管理
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  在 AcWing 网站上添加错题本管理功能。
// @author       CN059
// @match        https://www.acwing.com/*
// @license MIT
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // 初始化错题本数据
    let wrongNotebooks = GM_getValue('wrongNotebooks', {});

    // 获取当前页面的题目信息
    function getCurrentProblemInfo() {
        const titleElement = document.querySelector('.nice_font.problem-content-title');
        const algorithmTags = Array.from(document.querySelectorAll('.problem-algorithm-tag-field-item'))
            .map(tag => tag.textContent.trim());
        const sourceTags = Array.from(document.querySelectorAll('.problem-algorithm-source-field-item'))
            .map(tag => tag.textContent.trim());
        const difficultyElement = document.querySelector('.label.label-success.round, .label.label-warning.round, .label.label-danger.round');
        const problemLink = window.location.href;

        if (!titleElement || !algorithmTags.length) return null;

        return {
            title: titleElement.textContent.trim(),
            algorithms: algorithmTags,
            sources: sourceTags,
            difficulty: difficultyElement ? difficultyElement.textContent.trim() : '未知',
            link: problemLink,
            addedTime: new Date().toLocaleString()
        };
    }

    // 添加“添加到错题本”按钮
    function addAddToNotebookButton() {
        if (!document.querySelector('#submit_code_btn')) return;

        const menuDiv = document.querySelector('#submit_code_btn').parentNode;
        const addButton = document.createElement('button');
        addButton.textContent = '添加到错题本';
        addButton.className = 'btn btn-primary';
        addButton.style.float = 'right';
        addButton.style.borderRadius = '20px';
        addButton.style.margin = '20px 0 0 20px';

        addButton.addEventListener('click', () => {
            showAddToNotebookModal();
        });

        menuDiv.appendChild(addButton);
    }

    // 显示“添加到错题本”弹窗
    function showAddToNotebookModal() {
        const modal = document.createElement('div');
        modal.style.position = 'fixed';
        modal.style.top = '50%';
        modal.style.left = '50%';
        modal.style.transform = 'translate(-50%, -50%)';
        modal.style.width = '400px';
        modal.style.padding = '20px';
        modal.style.backgroundColor = '#fff';
        modal.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
        modal.style.zIndex = '9999';

        const header = document.createElement('div');
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';
        header.style.marginBottom = '20px';

        const title = document.createElement('h3');
        title.textContent = '添加到错题本';
        header.appendChild(title);

        const closeButton = document.createElement('button');
        closeButton.textContent = '关闭';
        closeButton.style.border = 'none';
        closeButton.style.backgroundColor = '#fff';
        closeButton.style.cursor = 'pointer';
        closeButton.addEventListener('click', () => {
            modal.remove();
        });
        header.appendChild(closeButton);

        const notebookList = document.createElement('ul');
        notebookList.style.listStyle = 'none';
        notebookList.style.padding = '0';
        notebookList.style.margin = '0';

        let selectedNotebook = null;

        const updateNotebookList = () => {
            notebookList.innerHTML = '';
            Object.keys(wrongNotebooks).forEach(notebookName => {
                const li = document.createElement('li');
                li.textContent = notebookName;
                li.style.padding = '10px';
                li.style.cursor = 'pointer';
                li.style.borderBottom = '1px solid #ddd';

                li.addEventListener('click', () => {
                    selectedNotebook = notebookName;
                    Array.from(notebookList.children).forEach(item => item.style.backgroundColor = '');
                    li.style.backgroundColor = '#f0f0f0';
                });

                notebookList.appendChild(li);
            });
        };

        const createNotebookButton = document.createElement('button');
        createNotebookButton.textContent = '新建错题本';
        createNotebookButton.style.width = '100%';
        createNotebookButton.style.marginTop = '10px';
        createNotebookButton.addEventListener('click', () => {
            const newNotebookName = prompt('请输入新错题本的名称:');
            if (newNotebookName && !wrongNotebooks[newNotebookName]) {
                wrongNotebooks[newNotebookName] = [];
                GM_setValue('wrongNotebooks', wrongNotebooks);
                updateNotebookList();
            }
        });

        const footer = document.createElement('div');
        footer.style.display = 'flex';
        footer.style.justifyContent = 'flex-end';
        footer.style.marginTop = '20px';

        const confirmButton = document.createElement('button');
        confirmButton.textContent = '添加';
        confirmButton.style.padding = '10px 20px';
        confirmButton.style.backgroundColor = '#5cb85c';
        confirmButton.style.color = '#fff';
        confirmButton.style.border = 'none';
        confirmButton.style.borderRadius = '5px';
        confirmButton.style.cursor = 'pointer';
        confirmButton.addEventListener('click', () => {
            if (!selectedNotebook) {
                alert('请选择一个错题本!');
                return;
            }

            const problemInfo = getCurrentProblemInfo();
            if (!problemInfo) {
                alert('无法获取当前题目信息,请刷新页面后重试!');
                return;
            }

            wrongNotebooks[selectedNotebook].push(problemInfo);
            GM_setValue('wrongNotebooks', wrongNotebooks);
            alert(`已将题目添加到错题本 "${selectedNotebook}" 中!`);
            modal.remove();
        });

        footer.appendChild(confirmButton);

        modal.appendChild(header);
        modal.appendChild(notebookList);
        modal.appendChild(createNotebookButton);
        modal.appendChild(footer);

        updateNotebookList();
        document.body.appendChild(modal);
    }

    // 创建可拖动的“查看错题本”按钮
    function createDraggableButton() {
        const button = document.createElement('div');
        button.textContent = '📖';
        button.style.position = 'fixed';
        button.style.width = '50px';
        button.style.height = '50px';
        button.style.borderRadius = '50%';
        button.style.backgroundColor = '#ff4d4d';
        button.style.color = '#fff';
        button.style.textAlign = 'center';
        button.style.lineHeight = '50px';
        button.style.cursor = 'pointer';
        button.style.zIndex = '9999';
        button.style.top = '20px';
        button.style.right = '20px';

        let isDragging = false;
        let offsetX = 0, offsetY = 0;

        button.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - button.offsetLeft;
            offsetY = e.clientY - button.offsetTop;
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                button.style.left = `${e.clientX - offsetX}px`;
                button.style.top = `${e.clientY - offsetY}px`;
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
        });

        button.addEventListener('click', () => {
            showWrongNotebooksWindow();
        });

        document.body.appendChild(button);
    }

    // 显示错题本窗口
    function showWrongNotebooksWindow() {
        const modal = document.createElement('div');
        modal.style.position = 'fixed';
        modal.style.top = '50%';
        modal.style.left = '50%';
        modal.style.transform = 'translate(-50%, -50%)';
        modal.style.width = '800px';
        modal.style.height = '600px';
        modal.style.backgroundColor = '#fff';
        modal.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
        modal.style.zIndex = '9999';
        modal.style.display = 'flex';
        modal.style.flexDirection = 'column';

        // 顶部操作栏
        const header = document.createElement('div');
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';
        header.style.padding = '10px';
        header.style.borderBottom = '1px solid #ddd';

        const title = document.createElement('h3');
        title.textContent = '错题本管理';
        header.appendChild(title);

        const closeButton = document.createElement('button');
        closeButton.textContent = '关闭';
        closeButton.style.border = 'none';
        closeButton.style.backgroundColor = '#fff';
        closeButton.style.cursor = 'pointer';
        closeButton.addEventListener('click', () => {
            modal.remove();
        });
        header.appendChild(closeButton);

        // 左侧:错题集列表
        const leftPanel = document.createElement('div');
        leftPanel.style.width = '200px';
        leftPanel.style.padding = '20px';
        leftPanel.style.borderRight = '1px solid #ddd';
        leftPanel.style.overflowY = 'auto';

        const notebookList = document.createElement('ul');
        notebookList.style.listStyle = 'none';
        notebookList.style.padding = '0';
        notebookList.style.margin = '0';

        let selectedNotebook = null;

        const updateNotebookList = () => {
            notebookList.innerHTML = '';
            Object.keys(wrongNotebooks).forEach(notebookName => {
                const li = document.createElement('li');
                li.textContent = notebookName;
                li.style.padding = '10px';
                li.style.cursor = 'pointer';
                li.style.borderBottom = '1px solid #ddd';

                li.addEventListener('click', () => {
                    selectedNotebook = notebookName;
                    Array.from(notebookList.children).forEach(item => item.style.backgroundColor = '');
                    li.style.backgroundColor = '#f0f0f0';
                    showNotebookContent(notebookName);
                });

                const deleteButton = document.createElement('span');
                deleteButton.textContent = '❌';
                deleteButton.style.float = 'right';
                deleteButton.style.cursor = 'pointer';
                deleteButton.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (confirm(`确定要删除错题本 "${notebookName}" 吗?`)) {
                        delete wrongNotebooks[notebookName];
                        GM_setValue('wrongNotebooks', wrongNotebooks);
                        updateNotebookList();
                        rightPanel.innerHTML = '<p>请选择一个错题本。</p>';
                    }
                });

                li.appendChild(deleteButton);
                notebookList.appendChild(li);
            });
        };

        const createNotebookButton = document.createElement('button');
        createNotebookButton.textContent = '新建错题本';
        createNotebookButton.style.width = '100%';
        createNotebookButton.style.marginTop = '10px';
        createNotebookButton.addEventListener('click', () => {
            const newNotebookName = prompt('请输入新错题本的名称:');
            if (newNotebookName && !wrongNotebooks[newNotebookName]) {
                wrongNotebooks[newNotebookName] = [];
                GM_setValue('wrongNotebooks', wrongNotebooks);
                updateNotebookList();
            }
        });

        const importButton = document.createElement('button');
        importButton.textContent = '导入错题本';
        importButton.style.width = '100%';
        importButton.style.marginTop = '10px';
        importButton.addEventListener('click', () => {
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = '.json';
            input.onchange = (event) => {
                const file = event.target.files[0];
                if (!file) return;

                const reader = new FileReader();
                reader.onload = (e) => {
                    try {
                        const importedData = JSON.parse(e.target.result);
                        if (Array.isArray(importedData)) {
                            // 单个错题本
                            const notebookName = prompt('请输入新错题本的名称:');
                            if (notebookName) {
                                wrongNotebooks[notebookName] = importedData;
                            }
                        } else {
                            // 多个错题本
                            Object.assign(wrongNotebooks, importedData);
                        }
                        GM_setValue('wrongNotebooks', wrongNotebooks);
                        updateNotebookList();
                        alert('导入成功!');
                    } catch (error) {
                        alert('导入失败:文件格式不正确!');
                    }
                };
                reader.readAsText(file);
            };
            input.click();
        });

        const exportAllButton = document.createElement('button');
        exportAllButton.textContent = '导出所有错题本';
        exportAllButton.style.width = '100%';
        exportAllButton.style.marginTop = '10px';
        exportAllButton.addEventListener('click', () => {
            const dataStr = JSON.stringify(wrongNotebooks, null, 2);
            const blob = new Blob([dataStr], { type: 'application/json' });
            const url = URL.createObjectURL(blob);

            const a = document.createElement('a');
            a.href = url;
            a.download = 'all_notebooks.json';
            a.click();

            URL.revokeObjectURL(url);
        });

        leftPanel.appendChild(notebookList);
        leftPanel.appendChild(createNotebookButton);
        leftPanel.appendChild(importButton);
        leftPanel.appendChild(exportAllButton);

        // 右侧:错题集内容
        const rightPanel = document.createElement('div');
        rightPanel.style.flexGrow = '1';
        rightPanel.style.padding = '20px';
        rightPanel.style.overflowY = 'auto';

        const showNotebookContent = (notebookName) => {
            rightPanel.innerHTML = '';
            const problems = wrongNotebooks[notebookName] || [];

            if (problems.length === 0) {
                rightPanel.innerHTML = '<p>该错题本暂无记录。</p>';
                return;
            }

            const table = document.createElement('table');
            table.style.width = '100%';
            table.style.borderCollapse = 'collapse';

            const exportButton = document.createElement('button');
            exportButton.textContent = '导出该错题本';
            exportButton.style.marginBottom = '10px';
            exportButton.addEventListener('click', () => {
                const dataStr = JSON.stringify(problems, null, 2);
                const blob = new Blob([dataStr], { type: 'application/json' });
                const url = URL.createObjectURL(blob);

                const a = document.createElement('a');
                a.href = url;
                a.download = `${notebookName}.json`;
                a.click();

                URL.revokeObjectURL(url);
            });
            rightPanel.appendChild(exportButton);

            problems.forEach((problem, index) => {
                const row = document.createElement('tr');
                row.style.borderBottom = '1px solid #ddd';
                row.style.padding = '10px 0';

                const numberCell = document.createElement('td');
                numberCell.textContent = index + 1;
                numberCell.style.width = '50px';
                numberCell.style.textAlign = 'center';

                const titleCell = document.createElement('td');
                const titleLink = document.createElement('a');
                titleLink.textContent = problem.title;
                titleLink.href = problem.link;
                titleLink.target = '_blank';
                titleLink.style.textDecoration = 'underline';
                titleLink.style.color = '#007bff';
                titleCell.appendChild(titleLink);

                const tagsCell = document.createElement('td');
                const renderTags = (tags, color) => {
                    tags.forEach(tag => {
                        const tagSpan = document.createElement('span');
                        tagSpan.textContent = tag;
                        tagSpan.style.marginRight = '5px';
                        tagSpan.style.marginBottom = '3px';
                        tagSpan.style.color = '#505050';
                        tagSpan.style.fontSize = '12px';
                        tagSpan.style.padding = '2.5px 12px';
                        tagSpan.style.backgroundColor = '#f4f4f4';
                        tagSpan.style.borderRadius = '15px';
                        tagSpan.style.border = `1px solid ${color}`;
                        tagSpan.style.cursor = 'pointer';
                        tagSpan.style.display = 'inline-block';
                        tagsCell.appendChild(tagSpan);
                    });
                };
                renderTags(problem.sources, 'lightgrey');
                renderTags(problem.algorithms, 'lightgrey');

                const difficultyCell = document.createElement('td');
                const difficultySpan = document.createElement('span');
                difficultySpan.textContent = problem.difficulty;
                difficultySpan.style.padding = '.2em .6em .3em';
                difficultySpan.style.fontSize = '75%';
                difficultySpan.style.fontWeight = '700';
                difficultySpan.style.lineHeight = '1';
                difficultySpan.style.color = '#fff';
                difficultySpan.style.textAlign = 'center';
                difficultySpan.style.whiteSpace = 'nowrap';
                difficultySpan.style.verticalAlign = 'baseline';
                difficultySpan.style.borderRadius = '1020px';
                difficultySpan.style.display = 'inline-block';
                difficultySpan.style.backgroundColor = getDifficultyColor(problem.difficulty);
                difficultyCell.appendChild(difficultySpan);
                difficultyCell.style.textAlign = 'right';

                const deleteCell = document.createElement('td');
                const deleteButton = document.createElement('button');
                deleteButton.textContent = '删除';
                deleteButton.style.padding = '5px 10px';
                deleteButton.style.backgroundColor = '#fff';
                deleteButton.style.color = '#000';
                deleteButton.style.border = '1px solid #ddd';
                deleteButton.style.borderRadius = '5px';
                deleteButton.style.cursor = 'pointer';

                deleteButton.addEventListener('click', () => {
                    if (deleteButton.textContent === '删除') {
                        deleteButton.textContent = '确定?';
                        deleteButton.style.backgroundColor = '#d9534f';
                        deleteButton.style.color = '#fff';
                    } else {
                        wrongNotebooks[notebookName].splice(index, 1);
                        GM_setValue('wrongNotebooks', wrongNotebooks);
                        showNotebookContent(notebookName);
                    }
                });

                deleteCell.appendChild(deleteButton);

                row.appendChild(numberCell);
                row.appendChild(titleCell);
                row.appendChild(tagsCell);
                row.appendChild(difficultyCell);
                row.appendChild(deleteCell);
                table.appendChild(row);
            });

            rightPanel.appendChild(table);
        };

        modal.appendChild(header);
        modal.appendChild(document.createElement('div')).style.display = 'flex';
        modal.lastChild.appendChild(leftPanel);
        modal.lastChild.appendChild(rightPanel);

        updateNotebookList();
        document.body.appendChild(modal);
    }

    // 根据难度获取颜色
    function getDifficultyColor(difficulty) {
        switch (difficulty) {
            case '简单': return '#5cb85c';
            case '中等': return '#f0ad4e';
            case '困难': return '#d9534f';
            default: return '#ccc';
        }
    }

    // 初始化脚本
    function init() {
        if (window.location.href.includes('/problem/content/')) {
            addAddToNotebookButton();
        }
        createDraggableButton();
    }

    init();
})();