洛谷挂机计划
洛谷挂机计划
前置:tampermonkey
// ==UserScript==
// @name Luogu AFK
// @namespace https://www.luogu.com.cn/user/2064565
// @version 1.0
// @description 在洛谷任意页面搜索用户、题目、题单、讨论、文章,支持文章内容全文搜索,结果高亮显示
// @author SDMCMVIP & Everix
// @match https://www.luogu.com.cn/*
// @grant GM.xmlHttpRequest
// @connect www.luogu.com.cn
// @license MIT
// @require https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==
(function($, undefined) {
'use strict';
$(function(){
// ==================== 日期格式化 ====================
Date.prototype.pattern = function(format) {
var date = {
"y+": this.getYear(),
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S+": this.getMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1
? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
}
}
return format;
};
// ==================== 设置管理 ====================
var settings = localStorage.getItem("lsaw_settings");
if(settings == undefined || settings == 'undefined')
settings = {};
else
settings = JSON.parse(settings);
var majorSettings = [
["lsawUserDisplay", true],
["lsawProblemDisplay", true],
["lsawOfficialListDisplay", false],
["lsawSelectListDisplay", false],
["lsawDiscussDisplay", true],
["lsawArticleDisplay", true],
["lsawProblemDisplayNumber", 50],
["lsawListDisplayNumber", 50],
["lsawArticleDisplayNumber", 20],
];
for(var i=0; i<majorSettings.length; i++){
var key = majorSettings[i][0], val = majorSettings[i][1];
if(settings[key] == undefined || typeof(settings[key]) != typeof(val))
settings[key] = val;
}
localStorage.setItem("lsaw_settings", JSON.stringify(settings));
// ==================== 原有讨论链接保存 ====================
let discuss_link = document.querySelectorAll('a');
for(let link of discuss_link){
if(link.href.indexOf('/discuss/show?postid')==-1) continue;
let d_id_l = link.href.match(/[0-9]/g);
let d_id = '';
for(let x of d_id_l) d_id += x;
GM.xmlHttpRequest({
method: "get",
url: `https://lgbbs.oiso.cf/show.php?url=https://www.luogu.com.cn/discuss/${d_id}`,
onload: function(res){
if(res.status != 200) return;
console.info(`[LSA-X] Successfully saved discuss: https://www.luogu.com.cn/discuss/${d_id}`)
}
});
}
// ==================== 样式注入 ====================
$("body").append(`
<style>
.searchAnywhere{
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 999;
transition: 0.2s;
color: white;
}
.searchAnywhereMain{
height: min(600px, 100% - 10px);
width: min(750px, 100% - 10px);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
}
.inputArea{
display: block;
width: 100%;
height: 48px;
color: #aaa;
position: relative;
transition: 0.2s;
margin-bottom: 10px;
}
.inputArea > input{
border-radius: 5px;
border: 2px solid #aaa;
height: 48px;
width: 100%;
font-size: 18px;
color: #aaa;
padding: 14px 24px;
outline: 0;
background: transparent;
box-sizing: border-box;
}
.inputArea.onHover > input, .inputArea.onFocus > input{
border: 2px solid white;
}
.inputArea.onHover, .inputArea.onFocus, .inputArea.withContent{
color: white !important;
}
.inputArea.onHover > input, .inputArea.onFocus > input, .inputArea.withContent > input{
color: white !important;
}
.inputArea.withIconLeft > input{
padding-left: 42px;
}
.inputArea.withIconRight > input{
padding-right: 42px;
}
.inputArea > div.iconLeft{
height: 48px;
width: 48px;
position: absolute;
display: grid;
place-items: center;
top: 0px;
left: 0px;
}
.inputArea > div.iconRight{
height: 48px;
width: 48px;
position: absolute;
display: grid;
place-items: center;
top: 0px;
right: 0px;
}
.inputArea > div.iconLeft > svg{
width: 20px !important;
height: 20px !important;
}
.inputArea > div.iconRight > svg{
width: 20px !important;
height: 20px !important;
}
.inputAreaSmall{
display: block;
width: 100%;
height: 30px;
color: #aaa;
position: relative;
transition: 0.2s;
}
.inputAreaSmall > input{
border-radius: 5px;
border: 2px solid #aaa;
height: 30px;
width: 100%;
font-size: 18px;
color: #aaa;
padding: 5px 10px;
outline: 0;
background: transparent;
box-sizing: border-box;
}
.inputAreaSmall.onHover > input, .inputAreaSmall.onFocus > input{
border: 2px solid white;
}
.inputAreaSmall.onHover, .inputAreaSmall.onFocus, .inputAreaSmall.withContent{
color: white !important;
}
.inputAreaSmall.onHover > input, .inputAreaSmall.onFocus > input, .inputAreaSmall.withContent > input{
color: white !important;
}
.userPurple{ color: #cf5bff; font-weight: bold; }
.userRed{ color: #e74c3c; font-weight: bold; }
.userOrange{ color: #e67e22; font-weight: bold; }
.userYellow{ color: #d9a71d; font-weight: bold; }
.userGreen{ color: #5eb95e; font-weight: bold; }
.userGray{ color: #aaa; font-weight: bold; }
.userCheater{ color: #d3961c; font-weight: bold; }
.userBlue{ color: #07a2f1; font-weight: bold; }
.userGold{ color: #f1c40f; font-weight: bold; }
.badgePurple{ background-color: #cf5bff; }
.badgeRed{ background-color: #e74c3c; }
.badgeOrange{ background-color: #e67e22; }
.badgeYellow{ background-color: #d9a71d; }
.badgeGreen{ background-color: #5eb95e; }
.badgeGray{ background-color: #999; }
.badgeCheater{ background-color: #d3961c; }
.badgeBlue{ background-color: #07a2f1; }
.badgeBlack{ background-color: #0e1d69; }
.badgeGold{ background-color: #f1c40f; }
.searchAnywhereContent{
color: white;
flex: 1;
scrollbar-width: none;
-ms-overflow-style: none;
overflow-x: hidden;
overflow-y: auto;
}
.searchAnywhereContent::-webkit-scrollbar { width: 0 !important; }
.searchUserCard, .searchProblemCard, .searchListCard, .searchArticleCard{
background: #444;
border-radius: 10px;
display: flex;
flex-direction: column;
cursor: pointer;
color: white;
padding: 10px;
line-height: 1.4;
margin-bottom: 10px;
border: 2px solid #888;
box-sizing: border-box;
text-decoration: none;
transition: all 0.2s;
}
.searchUserCard:hover, .searchProblemCard:hover, .searchListCard:hover, .searchArticleCard:hover{
border-color: #07a2f1;
background: #4a4a4a;
}
.searchCard.light{
border: 2px solid white;
}
.searchUserCardBody, .searchProblemCardBody, .searchListCardBody{
display: flex;
flex-direction: row;
align-items: center;
}
.searchUserCardImg{
height: 36px;
width: 36px;
border-radius: 50%;
margin-right: 10px;
background-size: cover;
}
.searchUserCardInfo > span:first-child{
font-size: 14px;
margin-bottom: 3px;
display: inline-block;
color: #bbb;
}
.searchProblemCard > div, .searchListCard > div{
width: 100%;
}
.searchProblemCard > div:last-child, .searchListCard > div:last-child{
margin-top: 5px;
}
.searchProblemCardTag, .searchListCardTag{
margin-right: 12px;
display: inline-flex;
flex-direction: row;
align-items: center;
line-height: 24px;
height: 24px;
overflow: hidden;
font-size: 13px;
}
.searchProblemCardTag svg, .searchListCardTag svg{
height: 16px !important;
width: 16px !important;
margin-right: 3px;
}
.problemTagInfo{
font-size: 13px;
padding: 2px 8px;
border-radius: 5px;
color: white;
display: inline-block;
}
.userBadgeInfo{
font-size: 12px;
padding: 2px 6px;
border-radius: 5px;
color: white;
font-weight: bold;
margin: 0px 3px;
display: inline-block;
}
.searchArticleTitle {
font-size: 15px;
font-weight: bold;
margin-bottom: 6px;
color: #07a2f1;
}
.searchArticleSnippet {
font-size: 12px;
color: #ccc;
margin-bottom: 6px;
line-height: 1.4;
}
.searchArticleMeta {
font-size: 11px;
color: #888;
display: flex;
justify-content: space-between;
align-items: center;
}
.searchArticleType {
background: #555;
padding: 2px 8px;
border-radius: 4px;
font-size: 10px;
}
mark {
background: #f1c40f;
color: #000;
padding: 0 2px;
border-radius: 3px;
}
.loadingSpinner {
display: inline-block;
width: 24px;
height: 24px;
border: 3px solid #fff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.searchAnywhereSettings{
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
transition: 0.2s;
color: white;
}
.searchAnywhereSettingsMain{
height: min(450px, 100% - 10px);
width: min(500px, 100% - 10px);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
}
.searchAnywhereSettingsContent{
flex: 1;
scrollbar-width: none;
-ms-overflow-style: none;
overflow-x: hidden;
overflow-y: auto;
padding: 10px;
}
.searchAnywhereSettingsBag{
margin-bottom: 12px;
user-select: none;
font-size: 16px;
position: relative;
line-height: 32px;
border-bottom: 1px solid #555;
padding-bottom: 8px;
}
.searchAnywhereClicky{
position: relative;
height: 30px;
width: 30px;
display: inline-block;
}
.searchAnywhereClicky div{
transition: 0.2s;
position: absolute;
top: 0px;
left: 0px;
cursor: pointer;
}
.searchAnywhereClicky div svg{
height: 26px;
width: 26px;
}
.searchAnywhereClicky[flag=false] .falseBlock{ opacity: 1; }
.searchAnywhereClicky[flag=true] .falseBlock{ opacity: 0; }
.searchAnywhereClicky[flag=true] .trueBlock{ opacity: 1; }
.searchAnywhereClicky[flag=false] .trueBlock{ opacity: 0; }
.searchAnywhereCloseSettings{
display: inline-grid;
place-items: center;
width: 36px;
height: 36px;
cursor: pointer;
border-radius: 50%;
background: white;
margin-top: 10px;
}
.searchAnywhereCloseSettings:hover{
background: #aaa;
}
.searchAnywhereSettingsContent::-webkit-scrollbar { width: 0 !important; }
</style>
<!-- 搜索面板 -->
<div class='searchAnywhere' style="opacity: 0; display: none;">
<div class='searchAnywhereMain'>
<div class='inputArea withIconLeft withIconRight searchAnywhereMainInput'>
<input spellcheck="false" placeholder="搜索用户、题目、讨论、文章..."/>
<div class="iconLeft"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"></path></svg></div>
<div class="iconRight"><svg style="cursor: pointer" class="searchAnywhereSettingsLink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M495.9 166.6C499.2 175.2 496.4 184.9 489.6 191.2L446.3 230.6C447.4 238.9 448 247.4 448 256C448 264.6 447.4 273.1 446.3 281.4L489.6 320.8C496.4 327.1 499.2 336.8 495.9 345.4C491.5 357.3 486.2 368.8 480.2 379.7L475.5 387.8C468.9 398.8 461.5 409.2 453.4 419.1C447.4 426.2 437.7 428.7 428.9 425.9L373.2 408.1C359.8 418.4 344.1 427 329.2 433.6L316.7 490.7C314.7 499.7 307.7 506.1 298.5 508.5C284.7 510.8 270.5 512 255.1 512C241.5 512 227.3 510.8 213.5 508.5C204.3 506.1 197.3 499.7 195.3 490.7L182.8 433.6C167 427 152.2 418.4 138.8 408.1L83.14 425.9C74.3 428.7 64.55 426.2 58.63 419.1C50.52 409.2 43.12 398.8 36.52 387.8L31.84 379.7C25.77 368.8 20.49 357.3 16.06 345.4C12.82 336.8 15.55 327.1 22.41 320.8L65.67 281.4C64.57 273.1 64 264.6 64 256C64 247.4 64.57 238.9 65.67 230.6L22.41 191.2C15.55 184.9 12.82 175.3 16.06 166.6C20.49 154.7 25.78 143.2 31.84 132.3L36.51 124.2C43.12 113.2 50.52 102.8 58.63 92.95C64.55 85.8 74.3 83.32 83.14 86.14L138.8 103.9C152.2 93.56 167 84.96 182.8 78.43L195.3 21.33C197.3 12.25 204.3 5.04 213.5 3.51C227.3 1.201 241.5 0 256 0C270.5 0 284.7 1.201 298.5 3.51C307.7 5.04 314.7 12.25 316.7 21.33L329.2 78.43C344.1 84.96 359.8 93.56 373.2 103.9L428.9 86.14C437.7 83.32 447.4 85.8 453.4 92.95C461.5 102.8 468.9 113.2 475.5 124.2L480.2 132.3C486.2 143.2 491.5 154.7 495.9 166.6V166.6zM256 336C300.2 336 336 300.2 336 255.1C336 211.8 300.2 175.1 256 175.1C211.8 175.1 176 211.8 176 255.1C176 300.2 211.8 336 256 336z"/></svg></div>
</div>
<div class='searchAnywhereContent'></div>
</div>
</div>
<!-- 设置面板 -->
<div class='searchAnywhereSettings' style="opacity: 0; display: none;">
<div class='searchAnywhereSettingsMain'>
<div class="searchAnywhereSettingsContent">
<div>
<div class="searchAnywhereSettingsBag" for="lsawUserDisplay">
显示用户
<div style="float: right" class="searchAnywhereClicky" flag="${settings.lsawUserDisplay}">
<div class="falseBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM384 80H64C55.16 80 48 87.16 48 96V416C48 424.8 55.16 432 64 432H384C392.8 432 400 424.8 400 416V96C400 87.16 392.8 80 384 80z"/></svg></div>
<div class="trueBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM339.8 211.8C350.7 200.9 350.7 183.1 339.8 172.2C328.9 161.3 311.1 161.3 300.2 172.2L192 280.4L147.8 236.2C136.9 225.3 119.1 225.3 108.2 236.2C97.27 247.1 97.27 264.9 108.2 275.8L172.2 339.8C183.1 350.7 200.9 350.7 211.8 339.8L339.8 211.8z"/></svg></div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawProblemDisplay">
显示题目
<div style="float: right" class="searchAnywhereClicky" flag="${settings.lsawProblemDisplay}">
<div class="falseBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM384 80H64C55.16 80 48 87.16 48 96V416C48 424.8 55.16 432 64 432H384C392.8 432 400 424.8 400 416V96C400 87.16 392.8 80 384 80z"/></svg></div>
<div class="trueBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM339.8 211.8C350.7 200.9 350.7 183.1 339.8 172.2C328.9 161.3 311.1 161.3 300.2 172.2L192 280.4L147.8 236.2C136.9 225.3 119.1 225.3 108.2 236.2C97.27 247.1 97.27 264.9 108.2 275.8L172.2 339.8C183.1 350.7 200.9 350.7 211.8 339.8L339.8 211.8z"/></svg></div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawOfficialListDisplay">
显示官方题单
<div style="float: right" class="searchAnywhereClicky" flag="${settings.lsawOfficialListDisplay}">
<div class="falseBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM384 80H64C55.16 80 48 87.16 48 96V416C48 424.8 55.16 432 64 432H384C392.8 432 400 424.8 400 416V96C400 87.16 392.8 80 384 80z"/></svg></div>
<div class="trueBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM339.8 211.8C350.7 200.9 350.7 183.1 339.8 172.2C328.9 161.3 311.1 161.3 300.2 172.2L192 280.4L147.8 236.2C136.9 225.3 119.1 225.3 108.2 236.2C97.27 247.1 97.27 264.9 108.2 275.8L172.2 339.8C183.1 350.7 200.9 350.7 211.8 339.8L339.8 211.8z"/></svg></div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawSelectListDisplay">
显示用户题单
<div style="float: right" class="searchAnywhereClicky" flag="${settings.lsawSelectListDisplay}">
<div class="falseBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM384 80H64C55.16 80 48 87.16 48 96V416C48 424.8 55.16 432 64 432H384C392.8 432 400 424.8 400 416V96C400 87.16 392.8 80 384 80z"/></svg></div>
<div class="trueBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM339.8 211.8C350.7 200.9 350.7 183.1 339.8 172.2C328.9 161.3 311.1 161.3 300.2 172.2L192 280.4L147.8 236.2C136.9 225.3 119.1 225.3 108.2 236.2C97.27 247.1 97.27 264.9 108.2 275.8L172.2 339.8C183.1 350.7 200.9 350.7 211.8 339.8L339.8 211.8z"/></svg></div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawDiscussDisplay">
显示讨论
<div style="float: right" class="searchAnywhereClicky" flag="${settings.lsawDiscussDisplay}">
<div class="falseBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM384 80H64C55.16 80 48 87.16 48 96V416C48 424.8 55.16 432 64 432H384C392.8 432 400 424.8 400 416V96C400 87.16 392.8 80 384 80z"/></svg></div>
<div class="trueBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM339.8 211.8C350.7 200.9 350.7 183.1 339.8 172.2C328.9 161.3 311.1 161.3 300.2 172.2L192 280.4L147.8 236.2C136.9 225.3 119.1 225.3 108.2 236.2C97.27 247.1 97.27 264.9 108.2 275.8L172.2 339.8C183.1 350.7 200.9 350.7 211.8 339.8L339.8 211.8z"/></svg></div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawArticleDisplay">
搜索文章内容
<div style="float: right" class="searchAnywhereClicky" flag="${settings.lsawArticleDisplay}">
<div class="falseBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM384 80H64C55.16 80 48 87.16 48 96V416C48 424.8 55.16 432 64 432H384C392.8 432 400 424.8 400 416V96C400 87.16 392.8 80 384 80z"/></svg></div>
<div class="trueBlock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM339.8 211.8C350.7 200.9 350.7 183.1 339.8 172.2C328.9 161.3 311.1 161.3 300.2 172.2L192 280.4L147.8 236.2C136.9 225.3 119.1 225.3 108.2 236.2C97.27 247.1 97.27 264.9 108.2 275.8L172.2 339.8C183.1 350.7 200.9 350.7 211.8 339.8L339.8 211.8z"/></svg></div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawProblemDisplayNumber">
题目展示数量 (1-50)
<div style="float: right; width: 150px">
<div class="inputAreaSmall withContent">
<input type="number" min="1" max="50" value="${settings.lsawProblemDisplayNumber}" class="withContent"/>
</div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawListDisplayNumber">
题单展示数量 (1-50)
<div style="float: right; width: 150px">
<div class="inputAreaSmall withContent">
<input type="number" min="1" max="50" value="${settings.lsawListDisplayNumber}" class="withContent"/>
</div>
</div>
</div>
<div class="searchAnywhereSettingsBag" for="lsawArticleDisplayNumber">
文章/讨论展示数量 (1-200)
<div style="float: right; width: 150px">
<div class="inputAreaSmall withContent">
<input type="number" min="1" max="200" value="${settings.lsawArticleDisplayNumber}" class="withContent"/>
</div>
</div>
</div>
</div>
</div>
<div style="margin-top: 10px; text-align: center; width: 100%;">
<div class="searchAnywhereCloseSettings">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512" style="transform: scale(1.2); width: 16px; height: 16px; color: rgb(231, 76, 60);"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
</div>
</div>
</div>
</div>
`);
// ==================== UI 事件绑定 ====================
$(".inputArea > input").focus(function(){
$(this).parent().addClass("onFocus");
}).blur(function(){
$(this).parent().removeClass("onFocus");
if($(this).val().length != 0)
$(this).parent().addClass("withContent");
else
$(this).parent().removeClass("withContent");
});
$(".inputArea").mouseenter(function(){
$(this).addClass("onHover");
}).mouseleave(function(){
$(this).removeClass("onHover");
});
$(".inputAreaSmall > input").focus(function(){
$(this).parent().addClass("onFocus");
}).blur(function(){
$(this).parent().removeClass("onFocus");
if($(this).val().length != 0)
$(this).parent().addClass("withContent");
else
$(this).parent().removeClass("withContent");
});
$(".inputAreaSmall").mouseenter(function(){
$(this).addClass("onHover");
}).mouseleave(function(){
$(this).removeClass("onHover");
});
// 设置开关点击事件
$(".searchAnywhereClicky").click(function(){
var a = $(this).attr("flag");
if(a == "false"){
a = true;
$(this).attr("flag", a);
}
else{
a = false;
$(this).attr("flag", a);
}
var settingsName = $(this).parent().attr("for");
settings[settingsName] = a;
localStorage.setItem("lsaw_settings", JSON.stringify(settings));
});
// 数字输入框变化事件(支持1-200范围)
$(".inputAreaSmall input").change(function(){
var a = Number($(this).val());
var settingsName = $(this).parent().parent().parent().attr("for");
// 根据不同设置项设置不同的范围
if (settingsName === "lsawArticleDisplayNumber") {
a = Math.min(200, Math.max(1, a));
$(this).val(a);
} else if (settingsName === "lsawProblemDisplayNumber" || settingsName === "lsawListDisplayNumber") {
a = Math.min(50, Math.max(1, a));
$(this).val(a);
}
settings[settingsName] = a;
localStorage.setItem("lsaw_settings", JSON.stringify(settings));
});
$(".searchAnywhereCloseSettings").click(function(){
$(".searchAnywhereSettings").css("opacity", "0");
setTimeout(() => {
$(".searchAnywhereSettings").css("display", "none");
}, 200);
});
$(".searchAnywhereSettingsLink").click(function(){
$(".searchAnywhereSettings").css("display", "block");
setTimeout(() => {
$(".searchAnywhereSettings").css("opacity", "1");
}, 20);
});
// ==================== 颜色辅助函数 ====================
const getColorFromPercent = (x, opa) => {
let r = 0, g = 0, b = 0;
let rr = 231, gg = 76, bb = 60;
let rrr = 82, ggg = 196, bbb = 26;
r = rr + (rrr - rr) * x;
g = gg + (ggg - gg) * x;
b = bb + (bbb - bb) * x;
r += (60 - r) * (1 - opa);
g += (60 - g) * (1 - opa);
b += (60 - b) * (1 - opa);
r = Math.floor(r);
g = Math.floor(g);
b = Math.floor(b);
return `rgb(${r}, ${g}, ${b})`;
};
const problemColors = [ "Gray", "Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Black" ];
const problemNames = [ "暂无评定", "入门", "普及-", "普及/提高-", "普及+/提高", "提高+/省选-", "省选/NOI-", "NOI/NOI+/CTSC" ];
// ==================== HTML 转义 ====================
function escapeHtml(str) {
if (!str) return '';
return String(str).replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
// ==================== 卡片渲染函数 ====================
// 获取题目状态图标
const getProblemStatus = (submitted, accepted) => {
if(!submitted && !accepted)
return `<span style="color: #aaa">−</span>`;
if(!accepted)
return `<span style="color: #e74c3c">✗</span>`;
return `<span style="color: #52c41a">✓</span>`;
};
// 获取CCF等级徽章
const getCCFLevel = (x) => {
if(x == null || x < 3) return "";
var color = "";
if(x <= 5) color = "#5eb95e";
else if(x <= 7) color = "#07a2f1";
else color = "#f1c40f";
return `<span style="display: inline-block; background: ${color}; color: #000; font-size: 10px; padding: 0 4px; border-radius: 10px; margin-left: 5px;">Lv.${x}</span>`;
};
// 渲染用户卡片
function renderUserCard(user) {
let color = user.color || 'Gray';
if (color === 'Cheater') user.badge = '作弊者';
return `
<a class="searchCard searchUserCard" data-href="/user/${user.uid}" style="display: block; text-decoration: none;">
<div class="searchUserCardBody">
<div class="searchUserCardImg" style="background: url(https://cdn.luogu.com.cn/upload/usericon/${user.uid}.png); background-size: cover;"></div>
<div class="searchUserCardInfo">
<span>UID ${user.uid}</span><br/>
<div style='display: flex; flex-direction: row; align-items: center; flex-wrap: wrap;'>
<span class="user${color}" style="line-height: 20px">${escapeHtml(user.name)}</span>
${getCCFLevel(user.ccfLevel)}
${user.badge ? `<span class='userBadgeInfo badge${color}'>${escapeHtml(user.badge)}</span>` : ''}
</div>
</div>
</div>
</a>
`;
}
// 渲染题目卡片
function renderProblemCard(problem) {
let statusHtml = getProblemStatus(problem.submitted, problem.accepted);
return `
<a class="searchCard searchProblemCard" data-href="/problem/${problem.pid}" style="display: block; text-decoration: none;">
<div class="searchProblemCardBody">
<div style="margin-right: 8px;">${statusHtml}</div>
<div style="flex: 1;">${escapeHtml(problem.title)}</div>
</div>
<div>
<span class='searchProblemCardTag'>📖 ${problem.pid}</span>
<span class='searchProblemCardTag'>📊 ${problem.totalSubmit}</span>
<span class='searchProblemCardTag'>✓ ${problem.totalAccepted}</span>
<div style='flex: 1; text-align: right'>
<span class="problemTagInfo badge${problemColors[problem.difficulty]}">${problemNames[problem.difficulty]}</span>
</div>
</div>
</a>
`;
}
// 渲染讨论卡片
function renderDiscussCard(discuss, keyword) {
let snippet = '';
if (discuss.content) {
snippet = discuss.content.substring(0, 150);
if (keyword) {
let regex = new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
snippet = snippet.replace(regex, '<mark>$1</mark>');
}
}
return `
<a class="searchCard searchArticleCard" data-href="/discuss/${discuss.id}" style="display: block; text-decoration: none;">
<div class="searchArticleTitle">💬 ${escapeHtml(discuss.title)}</div>
${snippet ? `<div class="searchArticleSnippet">${snippet}...</div>` : ''}
<div class="searchArticleMeta">
<span class="searchArticleType">讨论</span>
<span>👤 ${escapeHtml(discuss.author?.name || '未知')}</span>
<span>📅 ${discuss.createTime ? new Date(discuss.createTime * 1000).pattern("yyyy-MM-dd") : ''}</span>
</div>
</a>
`;
}
// 渲染文章卡片
function renderArticleCard(article, keyword) {
let snippet = article.summary || article.content || '';
if (snippet && keyword) {
let regex = new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
snippet = snippet.substring(0, 150).replace(regex, '<mark>$1</mark>');
}
return `
<a class="searchCard searchArticleCard" data-href="/article/${article.id}" style="display: block; text-decoration: none;">
<div class="searchArticleTitle">📝 ${escapeHtml(article.title)}</div>
${snippet ? `<div class="searchArticleSnippet">${snippet}...</div>` : ''}
<div class="searchArticleMeta">
<span class="searchArticleType">文章</span>
<span>👤 ${escapeHtml(article.author?.name || '未知')}</span>
<span>📅 ${article.createTime ? new Date(article.createTime * 1000).pattern("yyyy-MM-dd") : ''}</span>
</div>
</a>
`;
}
// 渲染题单卡片
function renderListCard(item, type, acceptedCounts = {}) {
let typeName = type === 'official' ? '官方题单' : '用户题单';
let acs = acceptedCounts[item.id] || 0;
let bgColor = type === 'official' ? getColorFromPercent(acs / item.problemCount, 0.3) : '#444';
let html = `
<a class="searchCard searchListCard" data-href="/training/${item.id}" style="display: block; text-decoration: none; background: ${bgColor}; position: relative;">
`;
if (type === 'official') {
html += `
<div style="height: 3px; position: absolute; top: 0; left: 0; right: 0; overflow: hidden; border-radius: 10px 10px 0 0;">
<div style="width: ${acs / item.problemCount * 100}%; background: ${getColorFromPercent(acs / item.problemCount, 1)}; height: 3px;"></div>
</div>
`;
}
html += `
<div class="searchListCardBody">
<div style="margin-right: 8px;">#${item.id}</div>
<div style="flex: 1;">${escapeHtml(item.title)}</div>
</div>
<div class="searchArticleMeta" style="margin-top: 5px;">
<span class="searchArticleType">${typeName}</span>
<span>📚 ${item.problemCount}题</span>
<span>⭐ ${item.markCount || 0}</span>
`;
if (type === 'select' && item.provider) {
html += `<span>👤 ${escapeHtml(item.provider.name)}</span>`;
}
html += `
</div>
</a>
`;
return html;
}
// ==================== 主搜索函数 ====================
var searchTimeout = null;
var currentHoverCard = undefined;
const changeHoverCard = (x, scroll = true) => {
$(".searchCard.light").removeClass("light");
if(x != undefined && scroll)
$(".searchAnywhereMainInput > input").blur();
if(x != undefined){
x.addClass("light");
if(scroll){
var heg = x[0].offsetTop;
var prr = x.parent().parent();
var scr = prr.scrollTop();
var r = heg - x.outerHeight() + 5, l = r - prr.outerHeight() + x.outerHeight() + 10;
scr = Math.max(l, Math.min(r, scr));
prr.scrollTop(scr);
}
}
if(scroll){
$(".searchCard").off('mouseenter mouseleave');
$(document).one('mousemove', function() {
$(".searchCard").hover(function(){
changeHoverCard($(this), false);
}, function(){
changeHoverCard(undefined, false);
});
});
}
currentHoverCard = x;
};
const searchInfo = () => {
if(searchTimeout) clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
var info = $(".inputArea > input").val();
info = $.trim(info);
if(info == ""){
$(".searchAnywhereContent").html("");
return;
}
$(".searchAnywhereContent").html(`
<div style="text-align: center; padding: 40px;">
<div class="loadingSpinner"></div>
<div style="margin-top: 15px; color: #aaa;">正在搜索 "${escapeHtml(info)}" ...</div>
</div>
`);
let results = {
users: [],
problems: [],
officialLists: [],
selectLists: [],
discusses: [],
articles: []
};
let completed = 0;
let totalRequests = 0;
if(settings.lsawUserDisplay) totalRequests++;
if(settings.lsawProblemDisplay) totalRequests++;
if(settings.lsawOfficialListDisplay) totalRequests++;
if(settings.lsawSelectListDisplay) totalRequests++;
if(settings.lsawDiscussDisplay || settings.lsawArticleDisplay) totalRequests += 2;
function checkComplete() {
completed++;
if(completed >= totalRequests) {
renderResults(results, info);
}
}
// 搜索用户
if(settings.lsawUserDisplay) {
$.ajax({
url: `/api/user/search?keyword=${encodeURIComponent(info)}`,
type: 'GET',
success: function(json){
results.users = json.users || [];
checkComplete();
},
error: () => { checkComplete(); }
});
}
// 搜索题目
if(settings.lsawProblemDisplay) {
$.ajax({
url: `/problem/list`,
type: 'GET',
headers: {"x-luogu-type": "content-only"},
data: { keyword: info, page: 1, type: "P|B|CF|SP|AT|UVA" },
success: function(json){
if(json.code === 200 && json.currentData) {
results.problems = json.currentData.problems?.result || [];
}
checkComplete();
},
error: () => { checkComplete(); }
});
}
// 搜索官方题单
if(settings.lsawOfficialListDisplay) {
$.ajax({
url: `/training/list`,
type: "GET",
data: { keyword: info, page: 1, type: "official" },
headers: {"x-luogu-type": "content-only"},
success: (json) => {
if(json.code === 200 && json.currentData) {
results.officialLists = json.currentData.trainings?.result || [];
results.officialAcceptedCounts = json.currentData.acceptedCounts || {};
}
checkComplete();
},
error: () => { checkComplete(); }
});
}
// 搜索用户题单
if(settings.lsawSelectListDisplay) {
$.ajax({
url: `/training/list`,
type: "GET",
data: { keyword: info, page: 1, type: "select" },
headers: {"x-luogu-type": "content-only"},
success: (json) => {
if(json.code === 200 && json.currentData) {
results.selectLists = json.currentData.trainings?.result || [];
}
checkComplete();
},
error: () => { checkComplete(); }
});
}
// 搜索讨论
if(settings.lsawDiscussDisplay) {
$.ajax({
url: `/discuss/list`,
type: 'GET',
headers: {"x-luogu-type": "content-only"},
data: { keyword: info, page: 1 },
success: function(data) {
if(data.code === 200 && data.currentData?.discusses) {
let maxNum = settings.lsawArticleDisplayNumber || 20;
results.discusses = data.currentData.discusses.slice(0, maxNum);
}
checkComplete();
},
error: () => { checkComplete(); }
});
} else {
if(settings.lsawArticleDisplay) checkComplete();
}
// 搜索文章
if(settings.lsawArticleDisplay) {
$.ajax({
url: `/article/list`,
type: 'GET',
headers: {"x-luogu-type": "content-only"},
data: { keyword: info, page: 1 },
success: function(data) {
if(data.code === 200 && data.currentData?.articles) {
let maxNum = settings.lsawArticleDisplayNumber || 20;
results.articles = data.currentData.articles.slice(0, maxNum);
}
checkComplete();
},
error: () => { checkComplete(); }
});
} else {
if(settings.lsawDiscussDisplay) checkComplete();
}
}, 400);
};
// 渲染所有结果
function renderResults(results, keyword) {
let html = '<div>';
let hasResults = false;
// 用户结果
if(results.users.length > 0) {
hasResults = true;
html += `<div style='text-align: left; margin: 10px 0 8px 0; font-size: 16px; font-weight: bold; border-left: 3px solid #07a2f1; padding-left: 10px;'>👤 用户 (${results.users.length})</div>`;
results.users.slice(0, 10).forEach(user => {
html += renderUserCard(user);
});
if(results.users.length > 10) {
html += `<div style="text-align: center; margin-bottom: 10px;"><a href="/user/search?keyword=${encodeURIComponent(keyword)}" target="_blank" style="color: #07a2f1;">查看更多用户 →</a></div>`;
}
}
// 题目结果
if(results.problems.length > 0) {
hasResults = true;
html += `<div style='text-align: left; margin: 10px 0 8px 0; font-size: 16px; font-weight: bold; border-left: 3px solid #07a2f1; padding-left: 10px;'>📖 题目 (${results.problems.length})</div>`;
let maxNum = settings.lsawProblemDisplayNumber || 50;
results.problems.slice(0, maxNum).forEach(problem => {
html += renderProblemCard(problem);
});
}
// 讨论结果
if(results.discusses.length > 0) {
hasResults = true;
html += `<div style='text-align: left; margin: 10px 0 8px 0; font-size: 16px; font-weight: bold; border-left: 3px solid #07a2f1; padding-left: 10px;'>💬 讨论 (${results.discusses.length})</div>`;
results.discusses.forEach(discuss => {
html += renderDiscussCard(discuss, keyword);
});
}
// 文章结果
if(results.articles.length > 0) {
hasResults = true;
html += `<div style='text-align: left; margin: 10px 0 8px 0; font-size: 16px; font-weight: bold; border-left: 3px solid #07a2f1; padding-left: 10px;'>📝 文章 (${results.articles.length})</div>`;
results.articles.forEach(article => {
html += renderArticleCard(article, keyword);
});
}
// 官方题单
if(results.officialLists.length > 0) {
hasResults = true;
html += `<div style='text-align: left; margin: 10px 0 8px 0; font-size: 16px; font-weight: bold; border-left: 3px solid #07a2f1; padding-left: 10px;'>📚 官方题单 (${results.officialLists.length})</div>`;
let maxNum = settings.lsawListDisplayNumber || 50;
results.officialLists.slice(0, maxNum).forEach(list => {
html += renderListCard(list, 'official', results.officialAcceptedCounts || {});
});
}
// 用户题单
if(results.selectLists.length > 0) {
hasResults = true;
html += `<div style='text-align: left; margin: 10px 0 8px 0; font-size: 16px; font-weight: bold; border-left: 3px solid #07a2f1; padding-left: 10px;'>📋 用户题单 (${results.selectLists.length})</div>`;
let maxNum = settings.lsawListDisplayNumber || 50;
results.selectLists.slice(0, maxNum).forEach(list => {
html += renderListCard(list, 'select');
});
}
if(!hasResults) {
html += `
<div style="text-align: center; padding: 50px 20px;">
<div style="font-size: 48px; margin-bottom: 15px;">🔍</div>
<div style="font-size: 18px; color: #ccc;">未找到相关内容</div>
<div style="font-size: 13px; color: #888; margin-top: 10px;">试试其他关键词,或检查设置中的搜索选项</div>
</div>
`;
}
html += '</div>';
$(".searchAnywhereContent").html(html);
// 绑定卡片点击事件
$(".searchCard").off('click').on('click', function(e) {
e.stopPropagation();
let href = $(this).data('href');
if(href) window.open(href, '_blank');
});
// 绑定悬停事件
$(".searchCard").hover(function(){
changeHoverCard($(this), false);
}, function(){
changeHoverCard(undefined, false);
});
}
// ==================== 搜索输入绑定 ====================
$(".searchAnywhereMainInput > input").off('input propertychange').on('input propertychange', function(){
if(searchTimeout) clearTimeout(searchTimeout);
searchTimeout = setTimeout(searchInfo, 500);
});
// ==================== 搜索面板开关 ====================
let searchAnywhereOpen = false;
let inject_interval = setInterval(function(){
if($('.search-wrap').length == 0) return;
let en = $('.user-nav .search-wrap').next();
$(".user-nav .search-wrap").remove();
en.css("margin-left", "10px");
en.off('click').on('click', function(){
if(!searchAnywhereOpen){
$(".searchAnywhere").css("display", "block");
setTimeout(() => {
$(".searchAnywhere").css("opacity", "1");
$(".searchAnywhereMainInput > input").focus();
}, 20);
} else {
$(".searchAnywhere").css("opacity", "0");
setTimeout(() => {
$(".searchAnywhere").css("display", "none");
}, 200);
}
searchAnywhereOpen = !searchAnywhereOpen;
});
clearInterval(inject_interval);
}, 500);
$(".searchAnywhere").off('click').on('click', () => {
$(".searchAnywhere").css("opacity", "0");
setTimeout(() => {
$(".searchAnywhere").css("display", "none");
}, 200);
searchAnywhereOpen = false;
});
$(".searchAnywhereMainInput").off('click').on('click', (event) => {
event.stopPropagation();
});
// ==================== 快捷键支持 ====================
$(document).off('keydown').on('keydown', function(event){
// Ctrl + ; 或 Cmd + ;
if((event.keyCode == 186) && (event.ctrlKey || event.metaKey)){
if(!searchAnywhereOpen){
$(".searchAnywhere").css("display", "block");
setTimeout(() => {
$(".searchAnywhere").css("opacity", "1");
$(".searchAnywhereMainInput > input").focus();
}, 20);
} else {
$(".searchAnywhere").css("opacity", "0");
setTimeout(() => {
$(".searchAnywhere").css("display", "none");
}, 200);
}
searchAnywhereOpen = !searchAnywhereOpen;
event.preventDefault();
}
if(searchAnywhereOpen){
// 上键
if(event.keyCode == 38){
if(currentHoverCard == undefined){
var lis = $(".searchCard");
if(lis.length == 0)
$(".searchAnywhereMainInput > input").focus();
else{
currentHoverCard = lis.eq(lis.length - 1);
changeHoverCard(currentHoverCard);
}
}
else{
currentHoverCard = currentHoverCard.prevAll(".searchCard").first();
if(currentHoverCard.length == 0){
$(".searchAnywhereMainInput > input").focus();
currentHoverCard = undefined;
}
changeHoverCard(currentHoverCard);
}
event.preventDefault();
}
// 下键
else if(event.keyCode == 40){
if(currentHoverCard == undefined){
var lis = $(".searchCard");
if(lis.length == 0)
$(".searchAnywhereMainInput > input").focus();
else{
currentHoverCard = lis.eq(0);
changeHoverCard(currentHoverCard);
}
}
else{
currentHoverCard = currentHoverCard.nextAll(".searchCard").first();
if(currentHoverCard.length == 0){
$(".searchAnywhereMainInput > input").focus();
currentHoverCard = undefined;
}
changeHoverCard(currentHoverCard);
}
event.preventDefault();
}
// 回车键
else if(event.keyCode == 13){
if(currentHoverCard != undefined && currentHoverCard.data('href')){
window.open(currentHoverCard.data('href'), "_blank");
}
event.preventDefault();
}
else if(event.keyCode == 9){
event.preventDefault();
}
else {
$(".searchAnywhereMainInput > input").focus();
}
}
});
console.log('[LSA] 全文搜索增强版已加载 | 支持搜索: 用户/题目/题单/讨论/文章 | 文章展示数量: 1-200');
});
})(window.jQuery.noConflict(true));
依旧修复洛谷

浙公网安备 33010602011771号