Luogu Search AnyWhere

Search AnyWhere in Luogu!

当前为 2022-03-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Luogu Search AnyWhere
  3. // @version 0.2
  4. // @description Search AnyWhere in Luogu!
  5. // @author tiger2005
  6. // @match https://www.luogu.com.cn/*
  7. // @icon https://cdn.luogu.com.cn/upload/usericon/3.png
  8. // @grant none
  9. // @license MIT
  10. // @require https://code.jquery.com/jquery-3.6.0.min.js
  11. // @namespace https://greasyfork.org/users/829530
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. var addedContent = false;
  17. let majorInterval = setInterval(function(){
  18. if($(".user-nav .search-wrap").length == 0)
  19. return;
  20. // clearInterval(majorInterval);
  21. var sicon = $(".user-nav .search-wrap").next();
  22. $(".user-nav .search-wrap").remove();
  23. sicon.css("margin-left", "10px");
  24. if(! addedContent){
  25.  
  26. $("body").append(`
  27. <style>
  28. .searchAnywhere{
  29. position: fixed;
  30. top: 0px;
  31. left: 0px;
  32. height: 100%;
  33. width: 100%;
  34. background-color: rgba(0, 0, 0, 0.8);
  35. z-index: 999;
  36. transition: 0.2s;
  37. color: white;
  38. }
  39. .searchAnywhereMain{
  40. height: 600px;
  41. width: 750px;
  42. position: absolute;
  43. top: 50%;
  44. left: 50%;
  45. transform: translate(-50%, -50%);
  46. display: flex;
  47. flex-direction: column;
  48. }
  49. .inputArea{
  50. display: block;
  51. width: 100%;
  52. height: 48px;
  53. color: #aaa;
  54. position: relative;
  55. transition: 0.2s;
  56. margin-bottom: 10px;
  57. }
  58. .inputArea > input{
  59. border-radius: 5px;
  60. border: 2px solid #aaa;
  61. height: 48px;
  62. width: 100%;
  63. font-size: 18px;
  64. color: #aaa;
  65. padding: 14px 24px;
  66. outline: 0;
  67. background: transparent;
  68. box-sizing: border-box;
  69. }
  70. .inputArea.onHover > input, .inputArea.onFocus > input{
  71. border: 2px solid white;
  72. }
  73. .inputArea.onHover, .inputArea.onFocus, .inputArea.withContent{
  74. color: white !important;
  75. }
  76. .inputArea.onHover > input, .inputArea.onFocus > input, .inputArea.withContent > input{
  77. color: white !important;
  78. }
  79. .inputArea.withIconLeft > input{
  80. padding-left: 42px;
  81. }
  82. .inputArea.withIconRight > input{
  83. padding-right: 42px;
  84. }
  85. .inputArea > div.iconLeft{
  86. height: 48px;
  87. width: 48px;
  88. position: absolute;
  89. display: grid;
  90. place-items: center;
  91. top: 0px;
  92. left: 0px;
  93. }
  94. .inputArea > div.iconLeft > svg{
  95. width: 20px !important;
  96. height: 20px !important;
  97. }
  98. .userPurple{
  99. color: #cf5bff;
  100. font-weight: bold;
  101. }
  102. .userRed{
  103. color: #e74c3c;
  104. font-weight: bold;
  105. }
  106. .userOrange{
  107. color: #e67e22;
  108. font-weight: bold;
  109. }
  110. .userYellow{
  111. color: #d9a71d;
  112. font-weight: bold;
  113. }
  114. .userGreen{
  115. color: #5eb95e;
  116. font-weight: bold;
  117. }
  118. .userGray{
  119. color: #aaa;
  120. font-weight: bold;
  121. }
  122. .userCheater{
  123. color: #d3961c;
  124. font-weight: bold;
  125. }
  126. .userBlue{
  127. color: #07a2f1;
  128. font-weight: bold;
  129. }
  130. .userGold{
  131. color: #f1c40f;
  132. font-weight: bold;
  133. }
  134. .badgePurple{
  135. background-color: #cf5bff;
  136. }
  137. .badgeRed{
  138. background-color: #e74c3c;
  139. }
  140. .badgeOrange{
  141. background-color: #e67e22;
  142. }
  143. .badgeYellow{
  144. background-color: #d9a71d;
  145. }
  146. .badgeGreen{
  147. background-color: #5eb95e;
  148. }
  149. .badgeGray{
  150. background-color: #999;
  151. }
  152. .badgeCheater{
  153. background-color: #d3961c;
  154. }
  155. .badgeBlue{
  156. background-color: #07a2f1;
  157. }
  158. .badgeBlack{
  159. background-color: #0e1d69;
  160. }
  161. .badgeGold{
  162. background-color: #f1c40f;
  163. }
  164. .searchAnywhereContent{
  165. color: white;
  166. flex: 1;
  167. scrollbar-width: none;
  168. -ms-overflow-style: none;
  169. overflow-x: hidden;
  170. overflow-y: auto;
  171. }
  172. .searchAnywhereContent::-webkit-scrollbar { width: 0 !important; }
  173. .searchUserCard{
  174. background: #444;
  175. border-radius: 10px;
  176. display: flex;
  177. flex-direction: column;
  178. cursor: pointer;
  179. color: white;
  180. padding: 10px;
  181. line-height: 1;
  182. margin-bottom: 10px;
  183. border: 2px solid #888;
  184. box-sizing: border-box;
  185. }
  186. .searchUserCard:hover{
  187. border: 2px solid white;
  188. }
  189. .searchUserCard > div{
  190. width: 100%;
  191. }
  192. .searchUserCardBody{
  193. display: flex;
  194. flex-direction: row;
  195. }
  196. .searchUserCardImg{
  197. height: 36px;
  198. width: 36px;
  199. border-radius: 50%;
  200. margin-right: 10px;
  201. }
  202. .searchUserCardInfo > span:first-child{
  203. font-size: 14px;
  204. margin-bottom: 3px;
  205. display: inline-block;
  206. color: #bbb;
  207. }
  208. .searchUserCardInfo > span:last-child{
  209. font-size: 20px;
  210. }
  211. .searchUserCardMedia{
  212. display: flex;
  213. flex-direction: row;
  214. }
  215. .searchUserCardMedia > div{
  216. margin-top: 5px;
  217. flex: 1;
  218. display: inline-block;
  219. height: 23px;
  220. verticle-align: center;
  221. }
  222. .searchUserCardMedia > div > div{
  223. padding: 4px;
  224. position: relative;
  225. display: inline-block;
  226. background: #777;
  227. margin-right: 15px;
  228. }
  229. .searchUserCardMedia > div > div:after{
  230. width: 10px;
  231. height: 100%;
  232. content: "";
  233. border: 5px;
  234. position: absolute;
  235. top: 0px;
  236. left: 100%;
  237. clip-path: polygon(0 0,100% 50%,0 100%);
  238. background-color: inherit;
  239. }
  240. .userBadgeInfo{
  241. font-size: 14px;
  242. padding: 2px 5px;
  243. border-radius: 5px;
  244. color: white;
  245. font-weight: bold;
  246. margin: 0px 3px;
  247. display: inline-block;
  248. }
  249. .searchProblemCard{
  250. background: #444;
  251. border-radius: 10px;
  252. display: flex;
  253. flex-direction: column;
  254. cursor: pointer;
  255. color: white;
  256. padding: 10px;
  257. line-height: 1;
  258. margin-bottom: 10px;
  259. border: 2px solid #888;
  260. box-sizing: border-box;
  261. }
  262. .searchProblemCard:hover{
  263. border: 2px solid white;
  264. }
  265. .searchProblemCard > div{
  266. width: 100%;
  267. display: flex;
  268. flex-direction: row;
  269. }
  270. .searchProblemCard > div:last-child{
  271. margin-top: 5px;
  272. }
  273. .searchProblemCardTag{
  274. margin-right: 12px;
  275. }
  276. .searchProblemCardTag > div{
  277. padding: 4px;
  278. position: relative;
  279. display: inline-block;
  280. background: #777;
  281. margin-right: 15px;
  282. }
  283. .searchProblemCardTag > div:after{
  284. width: 10px;
  285. height: 100%;
  286. content: "";
  287. border: 5px;
  288. position: absolute;
  289. top: 0px;
  290. left: 100%;
  291. clip-path: polygon(0 0,100% 50%,0 100%);
  292. background-color: inherit;
  293. }
  294. .problemTagInfo{
  295. font-size: 16px;
  296. padding: 4px 7px;
  297. border-radius: 5px;
  298. color: white;
  299. display: inline-block;
  300. }
  301. .searchProblemCardBody > div:first-child{
  302. margin-right: 5px;
  303. }
  304. </style>
  305. <div class='searchAnywhere' style="opacity: 0; display: none;">
  306. <div class='searchAnywhereMain'>
  307. <div class='inputArea withIconLeft'>
  308. <input spellcheck="false" placeholder="Search AnyWhere"/>
  309. <div class="iconLeft"><svg data-v-1ad550c8="" data-v-303bbf52="" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="search" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-search fa-w-24"><path data-v-1ad550c8="" data-v-303bbf52="" 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" class=""></path></svg></div>
  310. </div>
  311. <div class='searchAnywhereContent'>
  312. </div>
  313. </div>
  314. </div>
  315. `);
  316. addedContent = true;
  317. $(".inputArea > input").focus(function(){
  318. $(this).parent().addClass("onFocus");
  319. });
  320. $(".inputArea > input").blur(function(){
  321. $(this).parent().removeClass("onFocus");
  322. if($(this).val().length != 0)
  323. $(this).parent().addClass("withContent");
  324. else
  325. $(this).parent().removeClass("withContent");
  326. });
  327. $(".inputArea").mouseenter(function(){
  328. $(this).addClass("onHover");
  329. });
  330. $(".inputArea").mouseleave(function(){
  331. $(this).removeClass("onHover");
  332. });
  333. }
  334.  
  335. const problemColors = [ "Gray", "Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Black" ];
  336. const problemNames = [ "暂无评定", "入门", "普及-", "普及/提高-", "普及+/提高", "提高+/省选-", "省选/NOI-", "NOI/NOI+/CTSC" ];
  337. var searchTimeout = null;
  338. const searchInfo = () => {
  339. searchTimeout = null;
  340. var info = $(".inputArea > input").val();
  341. info = $.trim(info);
  342. if(info == ""){
  343. $(".searchAnywhereContent").html("");
  344. return;
  345. }
  346. $(".searchAnywhereContent").html(`<div><div style='text-align: center; margin-bottom: 10px; width: 100%; font-size: 20px;'>加载中……</div></div>`);
  347. $(".searchAnywhereContent > div").unbind('click').click((event) => {
  348. event.stopPropagation();
  349. })
  350. var userHtml = "";
  351. var problemHtml = "";
  352. var finishWorks = 0;
  353. const finishWork = () => {
  354. ++ finishWorks;
  355. if(finishWorks == 2){
  356. if(userHtml == "" && problemHtml == "")
  357. $(".searchAnywhereContent").html(`<div><div style='text-align: center; margin-bottom: 10px; width: 100%; font-size: 20px;'>未搜索到相关内容</div></div>`);
  358. else{
  359. $(".searchAnywhereContent").html(`<div>` + userHtml + problemHtml + `</div>`);
  360. $(".searchShowProblems").unbind("click").click(function(event){
  361. window.open(`https://www.luogu.com.cn/problem/list?keyword=${info}&page=1&type=P%7CB%7CCF%7CSP%7CAT%7CUVA`, "_blank");
  362. });
  363. $(".searchUserCard").unbind("click").click(function(event){
  364. window.open(`https://www.luogu.com.cn/user/${$(this).attr("uid")}`, "_blank");
  365. })
  366. $(".searchProblemCard").unbind("click").click(function(event){
  367. window.open(`https://www.luogu.com.cn/problem/${$(this).attr("pid")}`, "_blank");
  368. })
  369. $(".searchAnywhereContent > div").unbind('click').click((event) => {
  370. event.stopPropagation();
  371. })
  372. }
  373. }
  374. };
  375. const getProblemStatus = (x, y) => {
  376. if(!x && !y)
  377. return `<svg data-v-1b44b3e6="" data-v-c06fccc2="" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="minus" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="icon svg-inline--fa fa-minus fa-w-14" data-v-303bbf52="" style="width: 16px; height: 16px; color: #aaa"><path data-v-1b44b3e6="" fill="currentColor" d="M416 208H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h384c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z" class=""></path></svg>`;
  378. if(!y)
  379. return `<svg data-v-1b44b3e6="" data-v-c06fccc2="" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512" class="icon svg-inline--fa fa-times fa-w-11" data-v-303bbf52="" style="transform: scale(1.2); width: 16px; height: 16px; color: rgb(231, 76, 60);"><path data-v-1b44b3e6="" 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" class=""></path></svg>`;
  380. return `<svg data-v-1b44b3e6="" data-v-c06fccc2="" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="icon svg-inline--fa fa-check fa-w-16" data-v-303bbf52="" style="width: 16px; height: 16px; color: rgb(82, 196, 26);"><path data-v-1b44b3e6="" fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z" class=""></path></svg>`;
  381. }
  382. const getCCFLevel = (x) => {
  383. if(x == null || x < 3)
  384. return "";
  385. var color = "";
  386. if(x <= 5)
  387. color = "#5eb95e";
  388. else if(x <= 7)
  389. color = "#07a2f1";
  390. else
  391. color = "#f1c40f";
  392. return `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" style="margin: 0px 3px;" fill="${color}" style="margin-bottom: -3px;"><path d="M16 8C16 6.84375 15.25 5.84375 14.1875 5.4375C14.6562 4.4375 14.4688 3.1875 13.6562 2.34375C12.8125 1.53125 11.5625 1.34375 10.5625 1.8125C10.1562 0.75 9.15625 0 8 0C6.8125 0 5.8125 0.75 5.40625 1.8125C4.40625 1.34375 3.15625 1.53125 2.34375 2.34375C1.5 3.1875 1.3125 4.4375 1.78125 5.4375C0.71875 5.84375 0 6.84375 0 8C0 9.1875 0.71875 10.1875 1.78125 10.5938C1.3125 11.5938 1.5 12.8438 2.34375 13.6562C3.15625 14.5 4.40625 14.6875 5.40625 14.2188C5.8125 15.2812 6.8125 16 8 16C9.15625 16 10.1562 15.2812 10.5625 14.2188C11.5938 14.6875 12.8125 14.5 13.6562 13.6562C14.4688 12.8438 14.6562 11.5938 14.1875 10.5938C15.25 10.1875 16 9.1875 16 8ZM11.4688 6.625L7.375 10.6875C7.21875 10.8438 7 10.8125 6.875 10.6875L4.5 8.3125C4.375 8.1875 4.375 7.96875 4.5 7.8125L5.3125 7C5.46875 6.875 5.6875 6.875 5.8125 7.03125L7.125 8.34375L10.1562 5.34375C10.3125 5.1875 10.5312 5.1875 10.6562 5.34375L11.4688 6.15625C11.5938 6.28125 11.5938 6.5 11.4688 6.625Z"></path></svg>`
  393. }
  394. $.ajax({
  395. url: `/api/user/search?keyword=${info}`,
  396. type: 'GET',
  397. success: function(json){
  398. json = json.users;
  399. if(json.length != 0 && json[0] != null){
  400. userHtml = `
  401. <div style='text-align: left; margin-bottom: 10px; width: 100%; font-size: 18px; font-weight: bold'>用户</div>
  402. `;
  403. json.forEach((item) => {
  404. if(item == null)
  405. return;
  406. if(item.color == "Cheater")
  407. item.badge = "作弊者";
  408. userHtml += `
  409. <div class="searchUserCard" uid=${item.uid}>
  410. <div class="searchUserCardBody">
  411. <div class="searchUserCardImg" style="background: url(https://cdn.luogu.com.cn/upload/usericon/${item.uid}.png); background-size: 36px 36px;"></div>
  412. <div class="searchUserCardInfo"><span>UID ${item.uid}</span><br/><div style='display: flex; flex-direction: row'><span class="user${item.color}" style="line-height: 20px">${item.name}</span>${getCCFLevel(item.ccfLevel)}${item.badge != null && item.badge != "" ? `<div class='userBadgeInfo badge${item.color}'>${item.badge}</div>` : ""}</div></div>
  413. </div>
  414. </div>`
  415. });
  416. }
  417. finishWork();
  418. }
  419. });
  420. $.ajax({
  421. url: `/problem/list`,
  422. type: 'GET',
  423. headers: {"x-luogu-type": "content-only"},
  424. data: {
  425. keyword: info,
  426. page: 1,
  427. type: "P|B|CF|SP|AT|UVA"
  428. },
  429. success: function(json){
  430. json = json.currentData.problems;
  431. if(json.count != 0){
  432. problemHtml = `
  433. <div style='text-align: left; margin-bottom: 10px; width: 100%; font-size: 18px; font-weight: bold'>题目<div style="cursor: pointer; float: right; font-weight: normal !important" class="searchShowProblems">查看所有 ${json.count} 道题目</div></div>
  434. `;
  435. for(var i=0; i<json.result.length; i++){
  436. let item = json.result[i];
  437. problemHtml += `
  438. <div class="searchProblemCard" pid=${item.pid}>
  439. <div class="searchProblemCardBody">
  440. <div>${getProblemStatus(item.submitted, item.accepted)}</div>
  441. <div>${item.title}</div>
  442. </div>
  443. <div>
  444. <div class='searchProblemCardTag'><div>题号</div>${item.pid}</div>
  445. <div class='searchProblemCardTag'><div>尝试</div>${item.totalSubmit}</div>
  446. <div class='searchProblemCardTag'><div>通过</div>${item.totalAccepted}</div>
  447. <div style='flex: 1; text-align: right'>
  448. <div class="problemTagInfo badge${problemColors[item.difficulty]}">${problemNames[item.difficulty]}</div>
  449. </div>
  450. </div>
  451. </div>
  452. `;
  453. };
  454. }
  455. finishWork();
  456. }
  457. });
  458. };
  459. $(".inputArea > input").unbind('input propertychange').on('input propertychange', function(){
  460. if(searchTimeout != null)
  461. clearTimeout(searchTimeout);
  462. searchTimeout = setTimeout(searchInfo, 500);
  463. });
  464. let searchAnywhereOpen = false;
  465. sicon.unbind('click').click(function(){
  466. if(! searchAnywhereOpen){
  467. $(".searchAnywhere").css("display", "block");
  468. setTimeout(() => {
  469. $(".searchAnywhere").css("opacity", "1");
  470. }, 20);
  471. }
  472. else{
  473. $(".searchAnywhere").css("opacity", "0");
  474. setTimeout(() => {
  475. $(".searchAnywhere").css("display", "none");
  476. }, 200);
  477. }
  478. searchAnywhereOpen = !searchAnywhereOpen;
  479. });
  480. $(".searchAnywhere").unbind('click').click(() => {
  481. $(".searchAnywhere").css("opacity", "0");
  482. setTimeout(() => {
  483. $(".searchAnywhere").css("display", "none");
  484. }, 200);
  485. searchAnywhereOpen = false;
  486. })
  487. $(".inputArea").unbind('click').click((event) => {
  488. event.stopPropagation();
  489. })
  490. }, 500);
  491. })();