Greasy Fork

来自缓存

Greasy Fork is available in English.

我的搜索

订阅式搜索,打造属于自己的搜索引擎!

当前为 2022-12-29 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         我的搜索
// @namespace    http://tampermonkey.net/
// @version      2.3.0
// @description  订阅式搜索,打造属于自己的搜索引擎!
// @license MIT
// @author       zhuangjie
// @match      *://*/*
// @exclude  http://127.0.0.1*
// @exclude  http://localhost*
// @icon         data:image/png;base64,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
// @require      https://cdn.bootcdn.net/ajax/libs/jquery/3.6.2/jquery.min.js
// @grant        window.onurlchange
// @grant        GM_setValue
// @grant        GM_getValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand

// ==/UserScript==

(function() {
    'use strict';
    // 模块一:快捷键触发某一事件 (属于触发策略组)
    // 模块二:搜索视图(显示与隐藏)(属于搜索视图组)
    // 模块三:触发策略组触发策略触发搜索视图组视图
    // 模块四:根据用户提供的策略(策略属于数据生成策略组)生成搜索项的数据库
    // 模块五:视图接入数据库

    // 【函数库】
    // 数据缓存器
    let cache = {
        get(key) {
            return GM_getValue(key);
        },
        set(key,value) {
            GM_setValue(key,value);
        },
        remove(key) {
          GM_deleteValue(key);
        }
    }
    //防抖函数模板
    function debounce(fun, wait) {
        let timer = null;
        return function (...args) {
            // 清除原来的定时器
            if (timer) clearTimeout(timer)
            // 开启一个新的定时器
            timer = setTimeout(() => {
                fun.apply(this, args)
            }, wait)
        }
    }
    // 判断是否为指定指令
    function isInstructions(val,cmd) {
        return val == ":"+cmd;
    }
    // 全局注册表
    let ERROR = {
        tell(info) {
            console.error("ERROR " + info)
        }
    }

    //registry.searchData.SEARCH_DATA_KEY
    let registry = {
        view: {
            viewVisibilityController: () => { ERROR.tell("视图未初始化,但你使用了它的未初始化的注册表信息!") },
            viewDocument: null,
            setButtonVisibility: () => { ERROR.tell("按钮未初始化!") },

        },
        searchData: { //registry.searchData.subscribeKey
            data: [],
            subscribeKey: "subscribeKey",
            // 事件函数
            dataChange: [],
            SEARCH_DATA_KEY: "SEARCH_DATA_KEY"

        }
    }
    let dao = {}
    function showControlButton() {
        // 会显示一个按钮
        // 初始化按钮
        let viewDocument = registry.view.viewDocument;
        if(viewDocument == null) return;
        // 视图已初始化,可以显示按钮

    }
    function hideControlButton() {
       // 隐藏掉输入框右边按钮
    }

    // 实现模块一:使用快捷键触发指定事件
    function triggerAndEvent(goKeys = "ctrl+alt+s", fun, isKeyCode = false) {
        // 监听键盘按下事件

        let handle = function (event) {
            let isCtrl = goKeys.indexOf("ctrl") >= 0;
            let isAlt = goKeys.indexOf("alt") >= 0;
            let lastKey = goKeys.replaceAll("alt", "").replaceAll("ctrl", "").replace(/\++/gm,"").trim();
            // 判断 Ctrl+S
            if (event.ctrlKey != isCtrl || event.altKey != isAlt) return;
            if (!isKeyCode) {
                // 查看 lastKey == 按下的key
                if (lastKey.toUpperCase() == event.key.toUpperCase()) fun();
            } else {
                // 查看 lastKey == event.keyCode
                if (lastKey == event.keyCode) fun();
            }

        }
        // 如果使用 document.onkeydown 这种,只能有一个监听者
        $(document).keyup(handle);
    }

    // 【数据初始化】
    // 获取存在的订阅信息
    function getSubscribe() {
        // 查看是否有订阅信息
        let subscribeKey = registry.searchData.subscribeKey;
        let subscribeInfo = cache.get(subscribeKey);
        if(subscribeInfo == null ) {
           // 初始化订阅信息(初次)
           subscribeInfo = `
              <tis::https://raw.githubusercontent.com/18476305640/xiaozhuang/dev/%E5%B0%8F%E5%BA%84%E7%9A%84%E7%BD%91%E7%AB%99%E6%94%B6%E8%97%8F%E5%AE%A4.md fetchFun="sLineFetchFun" />
              <tis::https://raw.githubusercontent.com/18476305640/xiaozhuang/dev/%E5%B0%8F%E5%BA%84%E7%9A%84%E8%BD%AF%E4%BB%B6%E6%94%B6%E8%97%8F%E5%AE%A4.md fetchFun="sLineFetchFun" />
              <tis::https://raw.githubusercontent.com/18476305640/xiaozhuang/dev/Linux%E5%B8%B8%E7%94%A8%E8%BF%90%E7%BB%B4%E7%9F%A5%E8%AF%86.md fetchFun="mLineFetchFun" />
           `;
           cache.set(subscribeKey,subscribeInfo);
        }
        return subscribeInfo;
    }
    function editSubscribe(subscribe) {
       // 判断导入的订阅是否有效
       // 获取订阅信息(得到的值肯定不会为空)
       let pageTextHandleChainsY = pageTextHandleChains.init(subscribe);
       let tisHasFetchFun = pageTextHandleChainsY.parseSingleTab("tis","fetchFun");
       let tisNotFetchFun = pageTextHandleChainsY.parseSingleTabValue("tis");

       let tis = [...tisHasFetchFun, ...tisNotFetchFun];
       // 生成订阅信息存储
       let subscribeText = "\n";
       for(let aTis of tisHasFetchFun) {
          subscribeText += `<tis::${aTis.tabValue} fetchFun="${aTis.attrValue}" />\n`
       }
       for(let aTis of tisNotFetchFun) {
          subscribeText += `<tis::${aTis.tabValue} />\n`
       }
       // 持久化
       let newSubscribeInfo = subscribeText.replace(/\n+/gm,"\n\n");
       cache.set(registry.searchData.subscribeKey,newSubscribeInfo);
       return tis.length;
    }
    // 存储订阅信息,当指定 sLineFetchFun 时,表示将解析“直接页”的配置,如果没有指定 sLineFetchFun 时,只解析内容
    // 在提取函数中 \n 要改写为 \\n
    let dataSources = getSubscribe()+ `
       <fetchFun name="mLineFetchFun">
         function(pageText) {
              let type = "sketch"; // url   sketch
              let lines = pageText.split("\\n");
                let search_data_lines = []; // 扫描的搜索数据 {},{}
                let current_build_search_item = {};
                let current_build_search_item_resource = "";
                let point = 0; // 指的是上面的 current_build_search_item
                let default_desc = "--无描述--"
                function getTitleLineData(titleLine) {
                   const regex = /# ([^((\s]*)[((]?([^))\s]*)[))]?/gm;
                   // let test = /# ([^((\s]*)[((]?([^))\s]*)[))]?/gm.exec("# abc(abc)");
                   let matchData =  regex.exec(titleLine)
                   return {
                      title: matchData[1],
                      desc: ((matchData[2]==null || matchData[2] == "")?default_desc:matchData[2])
                   }
                }
                for (let line of lines) {
                    if(line.indexOf("# ") == 0) {
                       // 处理上一个item的结尾工作
                       if(point > 0) { // 必须有前一个item才处理
                          // 加入resource,最后一项
                          current_build_search_item.resource = current_build_search_item_resource;
                          // 打包装箱
                          search_data_lines.push(current_build_search_item);
                       }
                       // 当前新的开始工作
                       point++;
                       // 创建新的搜索项目容器
                       current_build_search_item = {...getTitleLineData(line)}
                       // 重置resource
                       current_build_search_item_resource = "";
                       continue;
                    }
                    // 向当前搜索项目容器追加当前行
                    current_build_search_item_resource += (line+"\\n");
                }
                // 添加种类
                for(let line of search_data_lines) {
                   line.type = type;
                }
                return search_data_lines;
         }
       </fetchFun>
       <fetchFun name="sLineFetchFun">
         function(pageText) {
              let type = "url"; // url   sketch
              let lines = pageText.split("\\n");
                let search_data_lines = []
                for (let line of lines) {
                    let search_data_line = (function(line) {
            let baseReg = "([^::]+)[((](.*)[))]\s*[::]\s*(.+)";
            let ifNotDescMatchReg = "([^::]+)\s*[::]\s*(.*)"
            let title = "";
            let desc = "";
            let resource = "";

         let captureResult = null;
         if( !(/[()()]/.test(line))) {
             // 兼容没有描述
             captureResult = line.match(ifNotDescMatchReg);
             if(captureResult == null ) return;
             title = captureResult[1];
             desc = "--无描述--";
            resource = captureResult[2];
         }else {
            // 正常语法
            captureResult = line.match(baseReg)
            if(captureResult == null ) return;
            title = captureResult[1];
            desc = captureResult[2];
            resource = captureResult[3];
         }
         return {
            title: title,
            desc: desc,
            resource: resource
         };
          })(line);
                    if (search_data_line == null || search_data_line.title == null) continue;
                    search_data_lines.push(search_data_line)
                }

                for(let line of search_data_lines) {
                   line.type = type;
                }
                return search_data_lines;
         }
      </fetchFun>
    `;


    // github CDN加速包装器
    function cdnPack(githubResourceUrl) {

       let githubUrlFlag = "raw.githubusercontent.com";

       // 如何不满足github url ,不加速
       if(githubResourceUrl.indexOf(githubUrlFlag) < 0) return githubResourceUrl;
       return "https://proxy.zyun.vip/"+githubResourceUrl;
    }

    // 模块四:初始化数据源


    // 使用责任链模式——对pageText进行操作的工具
    const pageTextHandleChains = {
        pageText: "",
        setPageText(newPageText) {
            this.pageText = newPageText;
        },
        getPageText() {
            return this.pageText;
        },
        init(newPageText = "") {
           // 深拷贝一份实例
           let wo = {...this};
           // 初始化
           wo.setPageText(newPageText);
           return wo;
        },
        // 解析双标签-获取指定标签下指定属性下的值
        parseDoubleTab(tabName,attrName) {
            // 返回指定标签下指定属性下的值
            const regex = RegExp(`<\\s*${tabName}[^<>]*\\s*${attrName}="([^<>]*)"\\s*>([\\s\\S]*?)<\/\\s*${tabName}\\s*>`,"gm");
            let m;
            let tabNameArr = [];
            let copyPageText = this.pageText;
            // 注意下面的 copyPageText 不能改变
            while ((m = regex.exec(copyPageText)) !== null) {
                // 这对于避免零宽度匹配的无限循环是必要的
                if (m.index === regex.lastIndex) {
                    regex.lastIndex++;
                }
                tabNameArr.push({
                    attrValue: m[1],
                    tabValue: m[2]
                })
                const newPageText =this.pageText.replace(m[0], "");
                this.pageText = newPageText;
            }
            return tabNameArr;
        },
            // 解析双标签-只获取值
          parseDoubleTabValue(tabName) {
                // 返回指定标签下指定属性下的值
                const regex = RegExp(`<\\s*${tabName}[^<>]*\\s*>([\\s\\S]*?)<\/\\s*${tabName}\\s*>`,"gm");
                let m;
                let tabNameArr = [];
                let copyPageText = this.pageText;
                while ((m = regex.exec(copyPageText)) !== null) {
                    // 这对于避免零宽度匹配的无限循环是必要的
                    if (m.index === regex.lastIndex) {
                        regex.lastIndex++;
                    }
                    tabNameArr.push({
                        tabValue: m[1]
                    })
                    const newPageText =this.pageText.replace(m[0], "");
                    this.pageText = newPageText;
                }

                return tabNameArr;
            },
                // 获取指定单标签指定属性与标签值(标签::值)
              parseSingleTab(tabName,attrName) {
                    // 返回指定标签下指定属性下的值
                    const regex = RegExp(`<${tabName}::([^\\s<>]*)\\s*${attrName}="([^"<>]*)"\\s*\/>`,"gm");
                    let m;
                    let tabNameArr = []
                    let copyPageText = this.pageText;
                    while ((m = regex.exec(copyPageText)) !== null) {
                        // 这对于避免零宽度匹配的无限循环是必要的
                        if (m.index === regex.lastIndex) {
                            regex.lastIndex++;
                        }
                        tabNameArr.push({
                            tabValue: m[1],
                            attrValue: m[2]
                        })

                        const newPageText =this.pageText.replace(m[0], "");
                        this.pageText = newPageText;
                    }

                    return tabNameArr;
                },
                   parseSingleTabValue(tabName) {
                        // 返回指定标签下指定属性下的值
                        const regex = RegExp(`<${tabName}::([^\\s<>]*)[^<>]*\/>`,"gm");
                        let m;
                        let tabNameArr = []
                        let copyPageText = this.pageText;
                        while ((m = regex.exec(copyPageText)) !== null) {
                            // 这对于避免零宽度匹配的无限循环是必要的
                            if (m.index === regex.lastIndex) {
                                regex.lastIndex++;
                            }
                            tabNameArr.push({
                                tabValue: m[1]
                            })
                            const newPageText =this.pageText.replace(m[0], "");
                            this.pageText = newPageText;
                        }
                        return tabNameArr;
                    },

                        // 清除指定单双标签
                  cleanTabByTabName(tabName) {
                            const regex = RegExp(`<\\s*${tabName}[^<>]*>([^<>]*)(<\/[^<>]*>)*`,"gm");
                            // 替换的内容
                            const subst = ``;
                            // 被替换的值将包含在结果变量中
                            const cleanedText = this.pageText.replace(regex, subst);
                            this.pageText = cleanedText;

                        }
    }
    // 从 订阅信息(或页) 中解析出配置(json)
    function getConfigFromDataSource(pageText) {

       let config = {
          // {url、fetchFun属性}
          tis: [],
          // {name与fetchFun属性}
          fetchFuns: []
       }
       // 从config中放在返回对象中
       let pageTextHandleChainsX = pageTextHandleChains.init(pageText);
       let fetchFunTabDatas = pageTextHandleChainsX.parseDoubleTab("fetchFun","name");
       for(let fetchFunTabData of fetchFunTabDatas) {
          config.fetchFuns.push( { name:fetchFunTabData.attrValue,fetchFun:fetchFunTabData.tabValue } )
       }
       // 获取tis
        let tisHasFetchFun = pageTextHandleChainsX.parseSingleTab("tis","fetchFun");
        let tisNotFetchFun = pageTextHandleChainsX.parseSingleTabValue("tis");
        let tisArr = [...tisHasFetchFun, ...tisNotFetchFun]
        for(let tis of tisArr) {
          config.tis.push( { url:tis.tabValue, fetchFun:tis.attrValue } )
        }

        return config;

    }
    // 将url转为文本(url请求得到的就是文本),当下面的dataSourceUrl不是http的url时,就会直接返回,不作请求
    function urlToText(dataSourceUrl) {
        // dataSourceUrl 转text
        return new Promise(function (resolve, reject) {
            if((dataSourceUrl.trim().indexOf("http") != 0 ) ) return resolve(dataSourceUrl+"?time="+new Date().getTime()) ;
            $.ajax({
                url: cdnPack(dataSourceUrl),
                success: function (result) {
                    resolve(result)
                }
            });
        });
    }
    // 下面的 dataSourceHandle 函数
    let globalFetchFun = [];
    let waitQueue = [];
    function dataSourceHandle(resourcePageUrl,tisTabFetchFunName) {
        urlToText(resourcePageUrl).then(text => {
            if(tisTabFetchFunName == null) {
                // --> 是配置 <--
                let data = []
                // 解析配置,是一个json
                let config = getConfigFromDataSource(text);
                console.log("解析的配置:",config)
                // 将FetchFun放到全局解析器中
                globalFetchFun.push(...config.fetchFuns);
                // 将tis放到处理队列中
                waitQueue.push(...config.tis);
                let tis = null;
                while((tis = waitQueue.pop()) != undefined) {
                    // tis有两个url,第二是fetchFun
                    dataSourceHandle(tis.url,tis.fetchFun);
                }
                // 清理内容
                pageTextHandleChains.setPageText("");
            }else {
                // --> 是内容 <--
                // 解析内容
                if(tisTabFetchFunName === "") return;
                let fetchFunStr = getFetchFunGetByName(tisTabFetchFunName);
                let search_data_line = (new Function('', "return (" + fetchFunStr + ")('" + text.replaceAll("\n","\\n") + "')"))();
                // 将之前修改为 <wrapLine> 改为真正的换行符 \n
                let replaceBefore = "\n";
                let replaceAfter = "\\n";
                for(let item of search_data_line) {
                    item.title =  item.title.replaceAll(replaceAfter,replaceBefore);
                    item.desc =  item.desc.replaceAll(replaceAfter,replaceBefore);
                    item.resource =  item.resource.replaceAll(replaceAfter,replaceBefore);
                }
                registry.searchData.data.push(...search_data_line);
                // 触发搜索数据改变事件
                for(let fun of registry.searchData.dataChange) {
                   fun();
                }
            }
        })


    }
    // 根据fetchFun名返回字符串函数
    function getFetchFunGetByName(fetchFunName) {
        for(let fetchFunData of globalFetchFun) {
           if(fetchFunData.name == fetchFunName) {
              return fetchFunData.fetchFun;
           }
        }
    }
    // 缓存数据
    function cacheSearchData() {
        console.log("触发了缓存,当前数据",registry.searchData.data)
        // 当有数据加入到全局数据容器时,会触发缓存,当前函数会执行
        let SEARCH_DATA_KEY = registry.searchData.SEARCH_DATA_KEY;
        cache.remove(SEARCH_DATA_KEY)
        cache.set(SEARCH_DATA_KEY,{
            data: registry.searchData.data,
            expire: new Date().getTime() + (1000*60*60*1) // 一个小时
        })

    }
    let initData = function () {
        // 从缓存中获取数据,判断是否还有效
        const SEARCH_DATA_KEY = registry.searchData.SEARCH_DATA_KEY;
        // cache.remove(SEARCH_DATA_KEY)
        let dataBox = cache.get(registry.searchData.SEARCH_DATA_KEY);
        if(dataBox != null) {
            // 缓存信息不为空,深入判断是否使用缓存的数据
            let dataExpireTime = dataBox.expire;
            let currentTime = new Date().getTime();
            // console.log("缓存的数据:",dataBox.data)
            // 数据多大时,才开启缓存
            const TRIGGER_CACHE_DATA_LENGTH = 300;
            // 判断是否有效,有效的话放到全局容器中
            let isValid = (dataExpireTime != null && dataExpireTime > currentTime && dataBox.data != null && dataBox.data.length > 0);
            // 如果网站比较特殊,忽略数据过期时间
            if(!isValid && window.location.host.toUpperCase().indexOf("GITHUB.COM") >= 0) {
               isValid = true;
            }
            // 如果数据量不满足缓存大小,会去请求数据
            if(isValid && dataBox.data.length >= TRIGGER_CACHE_DATA_LENGTH ) {
                registry.searchData.data = dataBox.data
                console.log("我的搜索:本次从缓冲中获取, 数据有效期还有"+parseInt((dataExpireTime - currentTime)/1000/60)+"分钟!" )
                return
            };

        }
        // 将去请求数据,需要将全局数据容器置空
        registry.searchData.data = [];
        // 内部将使用递归,解析出信息
        dataSourceHandle(dataSources,null);
        // 监听数据改变
        registry.searchData.dataChange.push(cacheSearchData)
    }
    // 模块二
    registry.view.viewVisibilityController = (function () {
        // 整个视图对象
        let viewDocument = null;
        let searchInputDocument = null;
        let matchItems = null;
        let searchBox = null;

        let isInitializedView = false;
        let viewName = "my_search_view"
        let controlButton = null;
        let initView = function () {
            // 初始化视图
            let view = document.createElement("div")
            view.innerHTML = (`
             <div id="${viewName}">
                <div id="searchBox">
                    <input placeholder="我的搜索" id="search_input" />
                    <button id="controlButton">Bug</button>
                </div>
                <div id="matchResult">
                    <ol id="matchItems">
                       <!-- <li><a href="#">webGL水族馆(测试电脑性能)</a></li> -->
                    </ol>
                </div>
             </div>
         `)
            // 设置样式
            view.style = `
             position: fixed;left: 25%;right: 25%;top:50px;
             border:2px solid #cecece;z-index:10000;
             background: #ffffff;
             overflow: hidden;
         `;

            // 挂载到文档中
            document.body.appendChild(view)
            // 整个视图对象放在组件全局中/注册表中
            registry.view.viewDocument = viewDocument = view;
            // 搜索框对象
            searchInputDocument = $("#search_input")
            matchItems = $("#matchItems");
            searchBox = $("#searchBox")
            controlButton = $("#controlButton")
            searchBox.css({
                "height": "45px",
                "background": "#ffffff",
                "padding": "0px",
                "box-sizing": " border-box",
                "z-index": "10001",
                "position":"relative"

            })
            searchInputDocument.css({
                "width": "100%",
                "height": "100%",
                "border": "none",
                "outline": "none",
                "font-size": "15px",
                "background": "#fff",
                "padding": "0px 10px",
                "box-sizing": " border-box"
            })
            $("#matchResult > ol").css({
                "margin": "0px",
                "padding": "0px 30px",
                "overflow": "hidden"

            })
            controlButton.css({
                "position": "absolute",
                "font-size":"12px !important",
                "right": "5px",
                "margin":"10px 7px",
                "padding":"3px 15px",
                "border-radius":"13.5px",
                "border":"none",
                "display":"none", // 默认隐藏,由函数控制
            })
            // 将输入框的控制按钮设置可见性函数公开放注册表中
            registry.view.setButtonVisibility = function (buttonVisibility = false) {
                // registry.view.setButtonVisibility
                controlButton.css({
                      "display": buttonVisibility?"block":"none"
                })
            }

            // 给输入框加事件
            // 执行 debounce 函数返回新函数
            let handler = function (e) {
                let key = e.target.value;
                let searchResultData = []
                let searchLevelData = [
                    [],[],[] // 分别是匹配标题/desc/url 的结果
                ]
                // 如果为空时,不作搜索
                if(key == "" || registry.searchData.data.length == 0 ) {
                    // 置空搜索
                    matchItems.html("")
                    return;
                }
                // 前置处理函数,这里使用观察者模式
                // searchPreFun(key);
                // 搜索操作
                let currentIndex = 0; // 数据项在总数据中的索引
                for (let dataItem of registry.searchData.data) {
                    key = key.toUpperCase();
                    // 将数据放在指定搜索层级数据上
                    if ((dataItem.title.toUpperCase().indexOf(key) >= 0 && searchLevelData[0].push(dataItem) )
                        || (dataItem.desc.toUpperCase().indexOf(key) >= 0 && searchLevelData[1].push(dataItem) )
                        || (dataItem.resource.toUpperCase().indexOf(key) >= 0 && searchLevelData[2].push(dataItem) ) ) {
                        // 向满足条件的数据对象添加在总数据中的索引
                        dataItem.index = currentIndex;
                    }
                    currentIndex++;
                }
                console.log("层级数据:",searchLevelData)
                // 将上面层级数据放在总容器中
                searchResultData.push(...searchLevelData[0])
                searchResultData.push(...searchLevelData[1])
                searchResultData.push(...searchLevelData[2])
                console.log("搜索总数据:",searchResultData)
                // 放到视图上
                // 置空内容
                matchItems.html("")
                // 最多显示条数
                let show_item_number = 15;
                for(let searchResultItem of searchResultData ) {
                    // 限制条数
                    if(show_item_number-- <= 0) {
                        break;
                    }
                    // 将符合的数据装载到视图
                    let item = `<li><a href="${searchResultItem.resource}" target="_blank" index="${searchResultItem.index}">${searchResultItem.type=="sketch"?"📖":""} ${searchResultItem.title}(${searchResultItem.desc})</a></li>`
                    matchItems.html(matchItems.html() + item)
                    currentIndex++;
                }
               
                // 设置li样式
                $("#matchResult li").css({
                    "line-height": "30px",
                    "color": "#0088cc",
                    "list-style": "decimal",
                    "width":"100%",
                    "margin":"0px"

                })
                $("#matchResult li>a").css({
                    "display":"block",
                    "font-size":"15px",
                    "color": "#5094d5",
                    "text-decoration":"none",
                    "text-align":"left",
                    "overflow":"hidden", //超出的文本隐藏
                    "text-overflow":"ellipsis", //溢出用省略号显示
                    "white-space":"nowrap" //溢出不换行
                })



            }
            // 给查询出来的结果添加事件 -- 设置事件委托
            $("#matchItems").on("click","li > a",function(e) {
                // 设置为阅读模式
                $("#search_input").val(":read");
                // 获取当前结果在搜索数组中的索引
                let dataIndex = parseInt($(e.target).attr("index"));
                let itemData = registry.searchData.data[dataIndex];
                // 如果是简述搜索信息,那就取消a标签的默认跳转事件
                if(itemData.resource.trim().indexOf("http") !== 0) {
                   // 取消默认事件
                   e.preventDefault();

                   alert("标题:"+itemData.title+"\n"+ "描述:"+itemData.desc+"\n"+"简述内容:\n"+itemData.resource);
                   return;
                }
                // 取消冒泡
                window.event? window.event.cancelBubble = true : e.stopPropagation();
                // 否则是URL跳转
            })
            const refresh = debounce(handler, 460)
            // 第一次触发 scroll 执行一次 fn,后续只有在停止滑动 1 秒后才执行函数 fn
            searchBox.on('input', refresh)

            // 初始化后将isInitializedView变量设置为true
            isInitializedView = true;
        }
        let hideView = function () {
            // 隐藏视图
            // 如果视图还没有初始化,直接退出
            if (!isInitializedView) return;
            // 让视图隐藏
            viewDocument.style.display = "none";
            // 将输入框内容置空
            searchInputDocument.val("")
            // 将之前搜索结果置空
            matchItems.html("")
        }
        let showView = function () {
            // 让视图可见
            viewDocument.style.display = "block";
            //聚焦
            searchInputDocument.focus()
            // 当输入框失去焦点时,隐藏视图
            searchInputDocument.blur(function() {
                // 判断输入框的内容是不是":debug"或是否正处于阅读模式,如果是,不隐藏
                if(isInstructions(searchInputDocument.val(),"debug") || isInstructions(searchInputDocument.val(),"read")) return;
                setTimeout(()=>{registry.view.viewVisibilityController(false)},200)
            });
        }

        // 返回给外界控制视图显示与隐藏
        return function (isSetViewVisibility) {
            if (isSetViewVisibility) {
                // 让视图可见 >>>
                // 如果还没初始化先初始化   // 初始化数据 initData();
                if (!isInitializedView) {
                    // 初始化视图
                    initView();
                    // 初始化数据
                    initData();
                }
                // 让视图可见
                showView();
            } else {
                // 隐藏视图 >>>
                if (isInitializedView) hideView();
            }
        }
    })();
    // 触发策略——快捷键
    let useKeyTrigger = function (viewVisibilityController) {
        // 将视图与触发策略绑定
        triggerAndEvent("ctrl+alt+s", function () {
            // 让视图可见
            viewVisibilityController(true);
        })
        triggerAndEvent("Escape", function () {
            // 让视图不可见
            viewVisibilityController(false);
        })
    }

    // 触发策略组
    let trigger_group = [useKeyTrigger];
    // 初始化入选的触发策略
    (function () {
        for (let trigger of trigger_group) {
            trigger(registry.view.viewVisibilityController);
        }
    })();




    // 打开视图进行配置
    // 显示配置视图
    // 是否显示进度 - 进度控制
    GM_registerMenuCommand("订阅管理",function() {
        showConfigView();
    });

    // 显示配置规则视图

    function showConfigView() {
        if($("#subscribe_save")[0] != null) return;
        // 显示视图
        var configViewContainer = document.createElement("div");
        configViewContainer.style=`
            width:450px; background:pink;
            position: fixed;right: 0px; top: 0px;
            z-index:10000;
            padding: 20px;
            border-radius: 14px;
        `
        configViewContainer.innerHTML = `
           <p id="title">订阅总览:</p>
           <textarea id="all_subscribe" ></textarea>
           <button id="subscribe_save" >保存并应用</button>
        `;

        // 设置样式
        document.body.appendChild(configViewContainer);
        document.getElementById("title").style="margin-bottom: 10px; font-size: 16px;";
        document.getElementById("all_subscribe").style="width:100%;height:150px";
        document.getElementById("subscribe_save").style=" margin-top: 20px; border: none; border-radius: 3px; padding: 4px 20px; cursor: pointer;";
        // 回显
        document.getElementById("all_subscribe").value = getSubscribe();
        // 保存
        document.getElementById("subscribe_save").onclick=function() {
            // 保存到对象
           let allSubscribe = document.getElementById("all_subscribe").value;
           let validCount = editSubscribe(allSubscribe);
           // 清除视图
           configViewContainer.remove();
           alert("保存配置成功!有效订阅数:"+validCount);
        }
    }

})();