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