技巧 - 写一个应用于Luogu的油猴插件

写一个应用于Luogu的油猴插件,拥有在任意页面上快捷搜索跳转题目的功能。

安装链接(插件发布地址): https://greasyfork.org/zh-CN/scripts/389291-luogu-quick-searcher

前言

应同机房大佬要求,做一个洛谷快捷题目搜索的油猴插件。

一些鲜为人知的历史

其实这个早在洛谷??.9版本(大概是18年底的一次UI更新)就写过一次了。

那段时间luogu把搜索框去掉了,以致于不能方便的搜索题目,才写了这个插件,并且使用luogu很早前搜索框的css。

过了半个月洛谷又更新了,然后右上角个人头像旁边多了搜索框。我当时直接退坑了,后来没多长时间插件就无法使用了。

为什么要重构插件

然后我发现洛谷新的搜索框非常不贴近人意(小声),然后同机房大佬又一次要求。

趁着没东西颓废一晚上颓了出来,恰好半年时间,我的JS、HTML能力已经大幅提高,所以这个插件做起来相当简单。

那现在和半年前有什么区别

首先洛谷把半年前已经废了的CSS都删了(包括半年前的搜索框CSS)。

并且洛谷的页面也有很大变化。误人子弟的说一句,好像洛谷现在是二次加载动态渲染?然后不用jQuery了?

然后是个人原因,现在的我有一定JS写代码能力,对一些基础语法比较熟练,熟悉很多方法。之前傻傻分不清jQuery和Web API的函数,只能一次一次测试来运用(哪个能用就用哪个)。

正题

可以先进入文章开头的链接查看介绍,也可以查看全部代码。

首先我们设定 F1 打开搜索框,若已打开就关闭,F1键码为112。注册按键:

1
document.onkeydown = function (event) { if (event.keyCode == 112) mainfunc(); };

mainfunc 函数就是用来打开关闭对话框。但是这个页面本来没有对话框,该怎么打开呢?

那么就需要新建,新建需要在网页中插入HTML。根据经验,我们需要在页面加载完成之后才能插入HTML,否则有不可预料的BUG。判断网页是否加载完成:

1
2
3
4
5
if (document.readyState == "complete") {
/*
Write your code here.
*/
}

然后就是新建元素、改样式、插入到网页中,这些很语法基础就直接给出,相关函数请自行了解。

我们选择把元素加入到页面的app(洛谷设计的)中。初始化:

1
2
3
4
5
6
7
8
9
10
11
var newElement = document.createElement("div");
newElement.id = "CiyangSearch";
newElement.innerHTML = "<input type = 'text'> | <a><i class = 'fas fa-search'></i></a> | <a><i class= 'fa fa-cog'></i></a>";
newElement.style.position = "fixed";
newElement.style.zIndex = "2";
newElement.style.top = "10%";
newElement.style.left = "30%";
newElement.style.width = "240px";
newElement.style.backgroundColor = "rgb(128,128,128)";
var appElement = document.getElementById('app');
appElement.appendChild(newElement);

上面的代码就已经可以打开一个搜索框,还有搜索按钮和设置按钮,但非常丑陋。

只有搜索框但是不能进行搜索,所以要绑定点击搜索按钮和回车键,回车键的键码为13。注册函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
newElement.children[0].onkeydown = function (event) {
if (event.keyCode == 13) {
var str = this.value;
/*
Write your code here.
*/
}
return;
};
newElement.children[1].onclick = function () {
var str = newElement.children[0].value;
/*
Write your code here.
*/
return;
};

str就是搜索框中输入的内容,获取后就可以跳转题目了。

我们想让它可自动识别是否为题目,如果是就打开题目页面,否则就打开题目列表进行搜索。

比如:输入P1001打开P1001题目页面,输入1001就进入题目列表搜索1001。

因为OI不学正则表达式,所以一般情况下我们想到if判断。这非常麻烦,会使代码复杂冗长,这个时候正则表达式就能大显身手了。

此处借鉴了另一个油猴插件Luogu Problem Jumper的匹配代码,应该是匹配洛谷题目最好的代码。

1
2
3
4
5
6
7
8
9
10
function judegeProblem(str) {
if (str.match(/AT[0-9]{1,4}/) == str) return true;
if (str.match(/CF[0-9]{1,4}[A-Z][0-9]{0,1}/) == str) return true;
if (str.match(/SP[0-9]{1,5}/) == str) return true;
if (str.match(/P[0-9]{4}/) == str) return true;
if (str.match(/UVA[0-9]{1,5}/) == str) return true;
if (str.match(/U[0-9]{1,6}/) == str) return true;
if (str.match(/T[0-9]{1,6}/) == str) return true;
return false;
}

接下来是打开页面,有两种方式,前者是从当前页跳转,后者是新标签页打开。

1
2
window.location = url;
window.open(url);

我们想要设计为用户选择首选方式,可首选其中一种方式,搜索时先输入 # 才能用优先级低的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var dUrl = 1;
/*

A lot of code.

*/
function judgeURL(way, str) {
if (str.length == 0) return;
if (judegeProblem(str)) go(dUrl ^ way, '/problemnew/show/' + str);
else go(dUrl ^ way, '/problem/list?keyword=' + str);
return;
}
function go(k, url) {
if (k == 0) window.location = url;
else window.open(url);
removek();
}

dUrl设为1就是首选新标签页,way表示是否先输入了 # 。

再回到绑定按键、点击搜索按钮的地方,比较懒所以代码没有合并到一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
newElement.children[0].onkeydown = function (event) {
if (event.keyCode == 13) {
var str = this.value;
if (str.length == 0) return;
if (str[0] == '#') judgeURL(1, str.substring(1, str.length));
else judgeURL(0, str);
}
return;
};
newElement.children[1].onclick = function () {
var str = newElement.children[0].value;
if (str.length == 0) return;
if (str[0] == '#') judgeURL(1, str.substring(1, str.length));
else judgeURL(0, str);
return;
};

主体基本完成,然后就是一些小功能。若打开搜索框,再次按下 F1 关闭。使设置打开页面方式更加可视化,完善设置按钮。

在 mainfuc 函数中,先加入一行代码:

1
if (removek()) return;

之前我们为了方便,将搜索框整体的元素ID改为了CiyangSearch,可以直接根据此ID来获取控件。代码简单:

1
2
3
4
5
6
7
8
function removek() {
var search = document.getElementById("CiyangSearch");
if (search) {
search.parentNode.removeChild(search);
return true;
}
return false;
}

完善设置按钮也很简单,首先在 mainfunc 绑定按键、搜索按钮处理下加入一行代码:

1
newElement.children[2].onclick = function () { setLink(); };

先介绍两个油猴自带的函数,GM_setValue和GM_getValue,可以非常方便的存储和读取数据,具体原理可以自行搜索。

点击设置按钮后跳出对话框,用户输入1或者0,设置首选方式。将之前的dUrl设置为读取数据。

1
2
3
4
5
6
7
8
9
10
11
var dUrl = GM_getValue("default_way");
function setLink() {
var defaultWay = prompt("请输入首选打开网页方式,1 为新标签页,0为从当前页跳转。当前为" + dUrl + ",搜索先输入 # 可使用优先级低的方式。");
if (defaultWay == null) return;
if (defaultWay != "0" && defaultWay != "1") {
alert("您的输入有误");
return;
}
GM_setValue("default_way", defaultWay);
location.reload();
}

我们还有需要一个使搜索的输入框自动获得焦点的小特性,只要在元素(控件)插入页面后加入一行代码:

1
newElement.children[0].focus();

然后8.23美化了一下UI,具体就不给出了。增加了设置背景颜色,换汤不换药。其中较为重要的修改:

1
2
3
4
5
6
7
8
9
10
11
12
var dUrl = GM_getValue("default_way"), bgColor = GM_getValue("searcher_bg");
if (bgColor == undefined) {
GM_setValue("searcher_bg", "rgba(135,206,235,0.5)");
bgColor = "rgba(135,206,235,0.5)";
}
newElement.innerHTML = "<input type = 'text'> | <i class = 'fas fa-search'></i> | <i class= 'fas fa-cog'></i> | <i class ='fas fa-crosshairs'></i> |";
function setBGColor() {
var searcherBG = prompt("请输入背景颜色码,支持RGB和十六进制。当前为" + bgColor + "。", bgColor);
if (searcherBG == null) return;
GM_setValue("searcher_bg", searcherBG);
location.reload();
}

分别处于代码各个位置,思路与设置打开方式类似,自行了解即可。

后记

还有什么问题可以在博客或发布网站评论提出,也可以用社交软件联系我。

在greasyfork发布,链接:https://greasyfork.org/zh-CN/scripts/389291-luogu-quick-searcher

如果时间充足还会一直更新,有要求也可以提出。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×