Greasy Fork

我的搜索

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

目前为 2023-01-01 提交的版本。查看 最新版本

// ==UserScript==
// @name         我的搜索
// @namespace    http://tampermonkey.net/
// @version      2.5.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.view.viewDocument
    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 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) {
            const baseReg = /([^::\\n]+)[((](.*)[))]\\s*[::]\s*(.+)/gm;
            const ifNotDescMatchReg = /([^::]+)\\s*[::]\\s*(.*)/gm;
            let title = "";
            let desc = "";
            let resource = "";

         let captureResult = null;
         if( !(/[()()]/.test(line))) {
             // 兼容没有描述
             captureResult = ifNotDescMatchReg.exec(line);
             if(captureResult == null ) return;
             title = captureResult[1];
             desc = "--无描述--";
            resource = captureResult[2];
         }else {
            // 正常语法
            captureResult = baseReg.exec(line);
            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) ;
            $.ajax({
                url: cdnPack(dataSourceUrl+"?time="+new Date().getTime()),
                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 = null;
    ( 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 textShow = null;
        let matchResult = 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 id="text_show">

                </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")
            textShow = $("#text_show")
            matchResult = $("#matchResult");
            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", // 默认隐藏,由函数控制
            })
            textShow.css({
                "display":"none",
                "width":"100%",
                "box-sizing": "border-box",
                "padding": "5px 10px 7px",
                "font-size": "13px",
                "line-height":"22px"
            })
            // 将输入框的控制按钮设置可见性函数公开放注册表中
            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++;
                }
                 // 隐藏文本显示视图
                textShow.css({
                    "display":"none"
                })
                // 让搜索结果显示
                matchResult.css({
                    "display":"block"
                })

                // 设置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();
                   matchResult.css({
                      "display": "none"
                   })
                   textShow.css({
                      "display":"block"
                   })
                   textShow.html("<span style='color:red'>标题</span>:"+itemData.title+"<br />"+ "<span style='color:red'>描述:</span>"+itemData.desc+"<br />"+"<span style='color:red'>简述内容:</span><br />"+itemData.resource.replace(/\n/gm,"<br />"))
                   return;
                }
                // 取消冒泡
                window.event? window.event.cancelBubble = true : e.stopPropagation();
                // 隐藏视图
                registry.view.viewVisibilityController(false)
                // 否则是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("")
            // 隐藏文本显示视图
            textShow.css({
                "display":"none"
            })
            // 让搜索结果显示
            matchResult.css({
              "display":"block"
            })
        }
        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);
        }
    }

})();