Luogu Search AnyWhere

Search AnyWhere in Luogu!

目前為 2022-03-31 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Luogu Search AnyWhere
// @version      0.2
// @description  Search AnyWhere in Luogu!
// @author       tiger2005
// @match        https://www.luogu.com.cn/*
// @icon         https://cdn.luogu.com.cn/upload/usericon/3.png
// @grant        none
// @license      MIT
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @namespace https://greasyfork.org/users/829530
// ==/UserScript==

(function() {
    'use strict';
    var addedContent = false;
    let majorInterval = setInterval(function(){
        if($(".user-nav .search-wrap").length == 0)
            return;
        // clearInterval(majorInterval);
        var sicon = $(".user-nav .search-wrap").next();
        $(".user-nav .search-wrap").remove();
        sicon.css("margin-left", "10px");
        if(! addedContent){

            $("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: 600px;
                        width: 750px;
                        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.iconLeft > svg{
                        width: 20px !important;
                        height: 20px !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{
                        background: #444;
                        border-radius: 10px;
                        display: flex;
                        flex-direction: column;
                        cursor: pointer;
                        color: white;
                        padding: 10px;
                        line-height: 1;
                        margin-bottom: 10px;
                        border: 2px solid #888;
                        box-sizing: border-box;
                    }
                    .searchUserCard:hover{
                        border: 2px solid white;
                    }
                    .searchUserCard > div{
                        width: 100%;
                    }
                    .searchUserCardBody{
                        display: flex;
                        flex-direction: row;
                    }
                    .searchUserCardImg{
                        height: 36px;
                        width: 36px;
                        border-radius: 50%;
                        margin-right: 10px;
                    }
                    .searchUserCardInfo > span:first-child{
                        font-size: 14px;
                        margin-bottom: 3px;
                        display: inline-block;
                        color: #bbb;
                    }
                    .searchUserCardInfo > span:last-child{
                        font-size: 20px;
                    }
                    .searchUserCardMedia{
                        display: flex;
                        flex-direction: row;
                    }
                    .searchUserCardMedia > div{
                        margin-top: 5px;
                        flex: 1;
                        display: inline-block;
                        height: 23px;
                        verticle-align: center;
                    }
                    .searchUserCardMedia > div > div{
                        padding: 4px;
                        position: relative;
                        display: inline-block;
                        background: #777;
                        margin-right: 15px;
                    }
                    .searchUserCardMedia > div > div:after{
                        width: 10px;
                        height: 100%;
                        content: "";
                        border: 5px;
                        position: absolute;
                        top: 0px;
                        left: 100%;
                        clip-path: polygon(0 0,100% 50%,0 100%);
                        background-color: inherit;
                    }
                    .userBadgeInfo{
                        font-size: 14px;
                        padding: 2px 5px;
                        border-radius: 5px;
                        color: white;
                        font-weight: bold;
                        margin: 0px 3px;
                        display: inline-block;
                    }
                    .searchProblemCard{
                        background: #444;
                        border-radius: 10px;
                        display: flex;
                        flex-direction: column;
                        cursor: pointer;
                        color: white;
                        padding: 10px;
                        line-height: 1;
                        margin-bottom: 10px;
                        border: 2px solid #888;
                        box-sizing: border-box;
                    }
                    .searchProblemCard:hover{
                        border: 2px solid white;
                    }
                    .searchProblemCard > div{
                        width: 100%;
                        display: flex;
                        flex-direction: row;
                    }
                    .searchProblemCard > div:last-child{
                        margin-top: 5px;
                    }
                    .searchProblemCardTag{
                        margin-right: 12px;
                    }
                    .searchProblemCardTag > div{
                        padding: 4px;
                        position: relative;
                        display: inline-block;
                        background: #777;
                        margin-right: 15px;
                    }
                    .searchProblemCardTag > div:after{
                        width: 10px;
                        height: 100%;
                        content: "";
                        border: 5px;
                        position: absolute;
                        top: 0px;
                        left: 100%;
                        clip-path: polygon(0 0,100% 50%,0 100%);
                        background-color: inherit;
                    }
                    .problemTagInfo{
                        font-size: 16px;
                        padding: 4px 7px;
                        border-radius: 5px;
                        color: white;
                        display: inline-block;
                    }
                    .searchProblemCardBody > div:first-child{
                        margin-right: 5px;
                    }
                </style>
                <div class='searchAnywhere' style="opacity: 0; display: none;">
                    <div class='searchAnywhereMain'>
                        <div class='inputArea withIconLeft'>
                            <input spellcheck="false" placeholder="Search AnyWhere"/>
                            <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>
                        </div>
                        <div class='searchAnywhereContent'>
                        </div>
                    </div>
                </div>
            `);
            addedContent = true;
            $(".inputArea > input").focus(function(){
                $(this).parent().addClass("onFocus");
            });
            $(".inputArea > input").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");
            });
            $(".inputArea").mouseleave(function(){
                $(this).removeClass("onHover");
            });
        }

        const problemColors = [ "Gray", "Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Black" ];
        const problemNames = [ "暂无评定", "入门", "普及-", "普及/提高-", "普及+/提高", "提高+/省选-", "省选/NOI-", "NOI/NOI+/CTSC" ];
        var searchTimeout = null;
        const searchInfo = () => {
            searchTimeout = null;
            var info = $(".inputArea > input").val();
            info = $.trim(info);
            if(info == ""){
                $(".searchAnywhereContent").html("");
                return;
            }
            $(".searchAnywhereContent").html(`<div><div style='text-align: center; margin-bottom: 10px; width: 100%; font-size: 20px;'>加载中……</div></div>`);
            $(".searchAnywhereContent > div").unbind('click').click((event) => {
                event.stopPropagation();
            })
            var userHtml = "";
            var problemHtml = "";
            var finishWorks = 0;
            const finishWork = () => {
                ++ finishWorks;
                if(finishWorks == 2){
                    if(userHtml == "" && problemHtml == "")
                        $(".searchAnywhereContent").html(`<div><div style='text-align: center; margin-bottom: 10px; width: 100%; font-size: 20px;'>未搜索到相关内容</div></div>`);
                    else{
                        $(".searchAnywhereContent").html(`<div>` + userHtml + problemHtml + `</div>`);
                        $(".searchShowProblems").unbind("click").click(function(event){
                            window.open(`https://www.luogu.com.cn/problem/list?keyword=${info}&page=1&type=P%7CB%7CCF%7CSP%7CAT%7CUVA`, "_blank");
                        });
                        $(".searchUserCard").unbind("click").click(function(event){
                            window.open(`https://www.luogu.com.cn/user/${$(this).attr("uid")}`, "_blank");
                        })
                        $(".searchProblemCard").unbind("click").click(function(event){
                            window.open(`https://www.luogu.com.cn/problem/${$(this).attr("pid")}`, "_blank");
                        })
                        $(".searchAnywhereContent > div").unbind('click').click((event) => {
                            event.stopPropagation();
                        })
                    }
                }
            };
            const getProblemStatus = (x, y) => {
                if(!x && !y)
                    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>`;
                if(!y)
                    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>`;
                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>`;
            }
            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 `<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>`
            }
            $.ajax({
                url: `/api/user/search?keyword=${info}`,
                type: 'GET',
                success: function(json){
                    json = json.users;
                    if(json.length != 0 && json[0] != null){
                        userHtml = `
                            <div style='text-align: left; margin-bottom: 10px; width: 100%; font-size: 18px; font-weight: bold'>用户</div>
                        `;
                        json.forEach((item) => {
                            if(item == null)
                                return;
                            if(item.color == "Cheater")
                                item.badge = "作弊者";
                            userHtml += `
                                <div class="searchUserCard" uid=${item.uid}>
                                <div class="searchUserCardBody">
                                    <div class="searchUserCardImg" style="background: url(https://cdn.luogu.com.cn/upload/usericon/${item.uid}.png); background-size: 36px 36px;"></div>
                                    <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>
                                </div>
                            </div>`
                        });
                    }
                    finishWork();
                }
            });
            $.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){
                    json = json.currentData.problems;
                    if(json.count != 0){
                        problemHtml = `
                            <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>
                        `;
                        for(var i=0; i<json.result.length; i++){
                            let item = json.result[i];
                            problemHtml += `
                             <div class="searchProblemCard" pid=${item.pid}>
                                <div class="searchProblemCardBody">
                                    <div>${getProblemStatus(item.submitted, item.accepted)}</div>
                                    <div>${item.title}</div>
                                </div>
                                <div>
                                    <div class='searchProblemCardTag'><div>题号</div>${item.pid}</div>
                                    <div class='searchProblemCardTag'><div>尝试</div>${item.totalSubmit}</div>
                                    <div class='searchProblemCardTag'><div>通过</div>${item.totalAccepted}</div>
                                    <div style='flex: 1; text-align: right'>
                                        <div class="problemTagInfo badge${problemColors[item.difficulty]}">${problemNames[item.difficulty]}</div>
                                    </div>
                                </div>
                            </div>
                            `;
                        };
                    }
                    finishWork();
                }
            });
        };
        $(".inputArea > input").unbind('input propertychange').on('input propertychange', function(){
            if(searchTimeout != null)
                clearTimeout(searchTimeout);
            searchTimeout = setTimeout(searchInfo, 500);
        });
        let searchAnywhereOpen = false;
        sicon.unbind('click').click(function(){
            if(! searchAnywhereOpen){
                $(".searchAnywhere").css("display", "block");
                setTimeout(() => {
                    $(".searchAnywhere").css("opacity", "1");
                }, 20);
            }
            else{
                $(".searchAnywhere").css("opacity", "0");
                setTimeout(() => {
                    $(".searchAnywhere").css("display", "none");
                }, 200);
            }
            searchAnywhereOpen = !searchAnywhereOpen;
        });
        $(".searchAnywhere").unbind('click').click(() => {
            $(".searchAnywhere").css("opacity", "0");
            setTimeout(() => {
                $(".searchAnywhere").css("display", "none");
            }, 200);
            searchAnywhereOpen = false;
        })
        $(".inputArea").unbind('click').click((event) => {
            event.stopPropagation();
        })
    }, 500);
})();