code-plus

解放鼠标,学码快人一步! 代码随想录网站辅助工具, 支持快速跳转到指定语言类型, 跳转到leetcode对应题目, 首次打开跳转到上次位置,切换前后文章

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         code-plus
// @namespace    http://tampermonkey.net/
// @version      0.6
// @description  解放鼠标,学码快人一步! 代码随想录网站辅助工具, 支持快速跳转到指定语言类型, 跳转到leetcode对应题目, 首次打开跳转到上次位置,切换前后文章
// @author       righthan
// @license      MIT
// @match        https://*.programmercarl.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    let lastKeyPressTime = 0; // 用于存储上一次按键的时间戳

    let page = document.querySelector('.page')
    let sider = document.querySelector('.page-sidebar')
    let siderLinks = document.querySelector('.sidebar-links')
    let isDoubleKeyG = false; // 标识是否是第一次按G键


    // 跳转到上次浏览的网页
    let historyPath = getCookie('history')
    // location.href条件:只在为根域名地址时跳转到历史(第一次打开的情况), 否则会导致无法跳转页面里的其他链接
    if (historyPath && location.href === 'https://www.programmercarl.com/') {
        location.href = historyPath // 因为默认不会加载侧边栏DOM, 所以第一次打开需要使用location.href来设定
        clickAndScrollNavLink(historyPath)
    } else {
        setCookie('history', location.href)
    }

    // 给导航容器列表添加监听器,检测网址变化
    siderLinks.addEventListener('click', (e) => { setCookie('history', e.target.href) })

    // 如果cookie中没有语言, 则需要设置
    let lang = getCookie('lang')
    if (!lang) {
        setCodeLanguage();
    }

    document.addEventListener("keyup", function (event) {
        // 检测是否快速连按了g开头的组合键
        const key = event.key
        if (key === 'g') {
            let currentTime = new Date().getTime(); // 更新第一次按g键的时间戳
            if (currentTime - lastKeyPressTime < 300) {
                isDoubleKeyG = true
            } else {
                isDoubleKeyG = false
            }
            lastKeyPressTime = currentTime
        }

        let currentTime = new Date().getTime(); // 获取当前时间戳
        let time = currentTime - lastKeyPressTime
        if (time < 400) { // 如果两次按键的时间间隔小于400毫秒,认为是快速连按
            switch (key) {
                case 'g':
                    lang = getCookie('lang')
                    if (isDoubleKeyG && lang) {
                        toCode(lang)
                        isDoubleKeyG = false
                    } else if (isDoubleKeyG && !lang) {
                        setCodeLanguage()
                    }
                    break
                case 'k':
                    // 跳转到上一篇文章
                    changeArticle(0)
                    break
                case 'j':
                    // 跳转到下一篇文章
                    changeArticle(1)
                    break
                case 'l':
                    toLeetCode()
                    break
                case 'c':
                    setCodeLanguage()
                    break
                case 'r':
                    toRelevant()
                    break
                case 't':
                    toTips()
                    break
                case 'h':
                    alert(`
                    code-plus是一款网页操作快捷辅助工具
                    按键及功能如下:
                    gg: 跳转到指定语言代码
                    gj: 跳转到下一篇
                    gk: 跳转到上一篇
                    gl: 跳转到leetcode页面
                    gt: 跳转到思路题目
                    gr: 跳转到相关题目
                    gc: 设置代码语言(保存在网站的cookie中)
                    gh: 显示本帮助页面
                    其他功能: 打开网页时恢复上次的进度
                    `)
                    break
            }
        }
    });

    // 跳转到指定语言的算法代码
    function toCode(langType) {
        // 获取对要滚动到的元素的引用
        let targetElement = page.querySelector('#' + langType);
        if (targetElement) { // 确保找到了元素
            // 使用scrollIntoView()方法将元素滚动到可见区域
            targetElement.scrollIntoView({
                behavior: "smooth", // 可选:使滚动平滑进行
                block: "start", // 可选:滚动到元素的顶部
            });
        } else {
            alert((langType !== 'c-2' ? langType : 'c#') + '语言的代码在此页面中不存在')
        }
    }

    // 文章跳转 flag:1表示下跳转到下一篇文章, 0表示上一篇文章
    function changeArticle(flag) {
        let optionButton = sider.querySelectorAll('div[title]')
        let path = optionButton[flag].childNodes[0].href
        setCookie('history', decodeURI(path)) // 记录路径
        clickAndScrollNavLink(decodeURI(path))
    }

    // 跳转到leetcode刷题网站
    function toLeetCode() {
        let link = page.querySelector('a[href*="leetcode"] ')
        if (link && link.href.includes('leetcode')) {
            link.click()
        } else {
            alert("当前页面可能没有LeetCode题目")
        }
    }

    // 设置代码语言
    function setCodeLanguage() {
        const supportLang = ['java', 'python', 'go', 'rust', 'javascript', 'typescript', 'swift', 'ruby', 'c', 'php', 'kotlin', 'scala', 'c#']
        let userInput = window.prompt("可能支持的语言:\n(一些语言代码不是每题都有, c++代码在靠前的位置, 不需要配置)\n" + supportLang.join('/') + "\n请输入需要快速跳转到的目标语言")

        if (userInput !== null) {
            if (supportLang.includes(userInput)) {
                // c#的id为c-2
                if (userInput === 'c#') { userInput = 'c-2' }
                setCookie('lang', userInput)
            } else {
                alert(userInput + "的语言类型似乎没有对应的代码, 请检查拼写, 并重新输入")
            }
        }
    }

    // 跳转到相关题目
    function toRelevant() {
        // 获取对要滚动到的元素的引用
        let targetElements = page.querySelectorAll('#相关题目, #相关题目推荐');
        if (targetElements.length > 0) { // 确保找到了元素
            // 使用scrollIntoView()方法将元素滚动到可见区域
            targetElements[0].scrollIntoView({
                behavior: "smooth", // 可选:使滚动平滑进行
                block: "start", // 可选:滚动到元素的顶部
            });
        } else {
            alert('无相关题目')
        }
    }

    // 跳转到思路
    function toTips() {
        // 获取对要滚动到的元素的引用
        let targetElements = page.querySelectorAll('#思路');
        if (targetElements.length > 0) { // 确保找到了元素
            // 使用scrollIntoView()方法将元素滚动到可见区域
            targetElements[0].scrollIntoView({
                behavior: "smooth", // 可选:使滚动平滑进行
                block: "start", // 可选:滚动到元素的顶部
            });
        } else {
            alert('当前页面没有思路内容')
        }
    }

    // 点击对应链接, 并且滚动侧边导航
    // 某次网站更新之后, 侧边栏导航的DOM不是加载全部, 所以只能在一个分类里面跳转了
    function clickAndScrollNavLink(path) {
        // 获取 https://www.programmercarl.com/kamacoder/xxx.html 中com之后的路径名
        const subPath = getPathAfterDomain(path)
        let targetElement = siderLinks.querySelector(`a[href*="${subPath}"]`)
        if (targetElement) { // 确保找到了元素
            targetElement.click()
            // 使用scrollIntoView() // 方法将元素滚动到可见区域
            targetElement.scrollIntoView({
                block: "start", // 可选:滚动到元素的顶部
            });
            document.querySelector('.sidebar').scrollBy({ top: -300 })
        }else{
            console.log(location.href)
            console.error('找不到目标元素')
            if( location.href !== 'https://www.programmercarl.com/'){
              alert('到达分类首部或末尾,或找不到目标元素');
            }
        }
    }

    // 获取域名之后的页面资源路径
    function getPathAfterDomain(url) {
        // 使用正则表达式匹配URL中的域名部分
        const domainRegex = /^https?:\/\/[^\/]+/;
        const match = url.match(domainRegex);

        // 如果没有匹配到域名部分,返回空字符串
        if (!match) {
            return '';
        }

        // 获取域名部分的长度
        const domainLength = match[0].length;

        // 返回URL中去除域名部分后的剩余路径
        return url.substring(domainLength);
    }

    // 解析特定的cookie值
    function getCookie(cookieName) {
        const allCookies = document.cookie;
        let name = cookieName + "=";
        let decodedCookie = decodeURIComponent(allCookies);
        let cookieArray = decodedCookie.split(';');
        for (let i = 0; i < cookieArray.length; i++) {
            let cookie = cookieArray[i];
            while (cookie.charAt(0) === ' ') {
                cookie = cookie.substring(1);
            }
            if (cookie.indexOf(name) === 0) {
                return cookie.substring(name.length, cookie.length);
            }
        }
        return "";
    }

    function setCookie(key, val) {
        document.cookie = `${key}=${val}; expires=Fri, 31 Dec 9999 23:59:59 GMT`
    }
})();