您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
无缝集成 划词搜索 + 快捷键搜索 + 搜索跳转 + 网址导航, 享受丝滑搜索体验
// ==UserScript== // @name Onestep Search - 一键快搜 // @namespace https://greasyfork.org/zh-CN/scripts/440000 // @version 2.1.5 // @author eyinwei // @description 无缝集成 划词搜索 + 快捷键搜索 + 搜索跳转 + 网址导航, 享受丝滑搜索体验 // @homepageURL https://github.com/eyinwei/Quick_Search // @icon https://s2.loli.net/2022/08/16/kwm38v2TxY4OtCs.png // @match *://*/* // @grant GM_openInTab // @grant GM_setClipboard // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @run-at document-end // @license MIT // @original-author smallx // ==/UserScript== (function () { 'use strict'; /////////////////////////////////////////////////////////////////// // 配置 /////////////////////////////////////////////////////////////////// //=========================定义网站数据======================================= function SiteInfo(_name, _url, _homepage, _icon) { this.name = _name; this.url = _url; this.home = _homepage; this.icon = _icon; this.callSiteInformation = function (_enable = true) { return { name: _name, url: _url, home: _homepage, icon: _icon, enable: _enable, }; }; }; // ========================= 前置统一定义所有网站 ========================= // 百度系列 const Baidu = new SiteInfo('百度', 'https://www.baidu.com/s?wd=%s&ie=utf-8', 'https://www.baidu.com/', 'https://www.baidu.com/favicon.ico'); const Baidufanyi = new SiteInfo('百度翻译', 'https://fanyi.baidu.com/#auto/zh/%s', 'https://fanyi.baidu.com/', 'https://fanyi-cdn.cdn.bcebos.com/webStatic/translation/img/favicon/favicon.ico'); const Baiduwangpan = new SiteInfo('百度网盘', 'https://pan.baidu.com/disk/home?#/search?key=%s', 'https://pan.baidu.com/', 'https://nd-static.bdstatic.com/m-static/v20-main/favicon-main.ico'); const Baidubaike = new SiteInfo('百度百科', 'https://baike.baidu.com/search/word?pic=1&sug=1&word=%s', 'https://baike.baidu.com/', 'https://baike.baidu.com/favicon.ico'); const Baiduzhidao = new SiteInfo('百度知道', 'https://zhidao.baidu.com/search?word=%s', 'https://zhidao.baidu.com/', 'https://www.baidu.com/favicon.ico?t=20171027'); const Baiduxinwen = new SiteInfo('百度新闻', 'https://www.baidu.com/s?rtt=1&bsst=1&cl=2&tn=news&rsv_dl=ns_pc&word=%s', 'http://news.baidu.com/', 'https://www.baidu.com/favicon.ico'); const Baiduwenku = new SiteInfo('百度文库', 'https://wenku.baidu.com/search?word=%s', '', 'https://www.baidu.com/favicon.ico'); const Baidumap = new SiteInfo('百度地图', 'https://map.baidu.com/search?querytype=s&wd=%s', '', 'https://map.baidu.com/favicon.ico'); const Baidutupian = new SiteInfo('百度图片', 'https://image.baidu.com/search/index?tn=baiduimage&ie=utf-8&word=%s', '', 'https://www.baidu.com/favicon.ico'); const Baiduxueshu = new SiteInfo('百度学术', 'http://xueshu.baidu.com/s?wd=%s', '', 'https://www.baidu.com/favicon.ico'); const Baidutieba = new SiteInfo('贴吧', 'https://tieba.baidu.com/f?kw=%s&ie=utf-8', 'https://tieba.baidu.com/', 'https://www.baidu.com/favicon.ico'); // 谷歌系列 const Google = new SiteInfo('谷歌', 'https://www.google.com/search?q=%s&ie=utf-8&oe=utf-8', 'https://www.google.com/', 'https://s2.loli.net/2022/08/16/QUL3cvA4t7Tx5sE.png'); const Googlefanyi = new SiteInfo('谷歌翻译', 'https://translate.google.com/?q=%s', '', 'https://ssl.gstatic.com/translate/favicon.ico'); const Googlemap = new SiteInfo('谷歌地图', 'https://www.google.com/maps/search/%s', 'https://www.google.com/maps/', 'https://s2.loli.net/2022/08/17/SloXZzf9nC6LPbq.png'); const Googleearth = new SiteInfo('谷歌地球', 'https://earth.google.com/web/search/%s', 'https://earth.google.com/web/', 'https://s2.loli.net/2022/08/17/IOiPDl7YX3QnmsC.png'); const Googlexueshu = new SiteInfo('谷歌学术', 'https://scholar.google.com/scholar?hl=zh-CN&q=%s', '', 'https://favicon.im/scholar.google.com'); const Googlepic = new SiteInfo('谷歌图片', 'https://www.google.com/search?q=%s&tbm=isch', 'https://www.google.com/imghp?hl=zh-CN', Google.icon); const Googlenews = new SiteInfo('谷歌新闻', 'https://news.google.com/search?q=%s&hl=zh-CN&gl=CN&ceid=CN:zh-Hans', 'https://news.google.com/topstories?hl=zh-CN&gl=CN&ceid=CN:zh-Hans', 'https://s2.loli.net/2022/08/17/RTdZQMD2Aw8eobn.png'); // 其他常用网站 const StackOverflow = new SiteInfo('StackOverflow', 'https://stackoverflow.com/search?q=%s', '', 'https://favicon.im/stackoverflow.com'); const Zhihu = new SiteInfo('知乎', 'https://www.zhihu.com/search?q=%s', 'https://www.zhihu.com/', 'https://static.zhihu.com/heifetz/favicon.ico'); const Bing = new SiteInfo('必应', 'https://cn.bing.com/search?q=%s', 'https://cn.bing.com/', 'https://s2.loli.net/2022/08/16/3uWMUjDVAlS8c9T.png'); const Bilibili = new SiteInfo('哔哩哔哩', 'https://search.bilibili.com/all?keyword=%s', 'https://www.bilibili.com/', 'https://www.bilibili.com/favicon.ico?v=1'); const Taobao = new SiteInfo('淘宝', 'https://s.taobao.com/search?q=%s', 'https://www.taobao.com/', 'https://www.taobao.com/favicon.ico'); const Jingdong = new SiteInfo('京东', 'https://search.jd.com/Search?keyword=%s&enc=utf-8', 'https://www.jd.com/', 'https://search.jd.com/favicon.ico'); const Tianmao = new SiteInfo('天猫', 'https://list.tmall.com/search_product.htm?q=%s', 'https://www.tmall.com/', 'https://www.tmall.com/favicon.ico'); const Maimai = new SiteInfo('脉脉', 'https://maimai.cn/web/search_center?type=gossip&query=%s&highlight=true', 'https://maimai.cn/feed_list', 'https://maimai.cn/favicon.ico'); const Weibo = new SiteInfo('微博', 'https://s.weibo.com/weibo/%s', 'https://weibo.com/', 'https://s.weibo.com/favicon.ico'); const GitHub = new SiteInfo('GitHub', 'https://github.com/search?q=%s','https://github.com', 'https://favicon.im/github.com'); const Sougou = new SiteInfo('搜狗', 'https://www.sogou.com/web?query=%s','https://www.sogou.com', 'https://favicon.im/www.sogou.com'); const So360 = new SiteInfo('360', 'https://www.so.com/s?ie=utf-8&q=%s', 'https://www.so.com/', 'https://s.ssl.qhimg.com/static/121a1737750aa53d.ico'); const Quora = new SiteInfo('Quora', 'https://www.quora.com/search?q=%s', 'https://www.quora.com/', 'https://favicon.im/www.quora.com'); const Wikipedia = new SiteInfo('维基百科', 'https://zh.wikipedia.org/wiki/%s', 'https://zh.wikipedia.org/', 'https://favicon.im/zh.wikipedia.org'); const Moegirl = new SiteInfo('萌娘百科', 'https://zh.moegirl.org/%s', 'https://zh.moegirl.org.cn/', 'https://zh.moegirl.org.cn/favicon.ico'); const Docin = new SiteInfo('豆丁文档', 'https://www.docin.com/search.do?searchcat=2&searchType_banner=p&nkey=%s', 'https://www.docin.com/', 'https://www.docin.com/favicon.ico'); const DoubanBook = new SiteInfo('豆瓣读书', 'https://search.douban.com/book/subject_search?search_text=%s', 'https://book.douban.com/', 'https://www.douban.com/favicon.ico'); const WeixinSogou = new SiteInfo('微信(搜狗)', 'https://weixin.sogou.com/weixin?ie=utf8&type=2&query=%s', 'https://weixin.sogou.com/', 'https://dlweb.sogoucdn.com/translate/favicon.ico?v=20180424'); const Guokr = new SiteInfo('果壳', 'https://www.guokr.com/search/all/?wd=%s', 'https://www.guokr.com/', 'https://www.guokr.com/favicon.ico'); const ApacheIssues = new SiteInfo('Apache Issues', 'https://issues.apache.org/jira/secure/QuickSearch.jspa?searchString=%s', 'https://issues.apache.org/jira/', 'https://favicon.im/issues.apache.org'); const Maven = new SiteInfo('Maven', 'https://mvnrepository.com/search?q=%s', 'https://mvnrepository.com/', 'https://favicon.im/mvnrepository.com'); const Youdao = new SiteInfo('有道词典', 'https://youdao.com/w/%s', 'https://youdao.com/', 'https://shared-https.ydstatic.com/images/favicon.ico'); const Bingfanyi = new SiteInfo('必应翻译', 'https://cn.bing.com/dict/search?q=%s', 'https://www.bing.com/dict', Bing.icon); const Haici = new SiteInfo('海词词典', 'http://dict.cn/%s', 'http://dict.cn/', 'http://i1.haidii.com/favicon.ico'); const CNKIfanyi = new SiteInfo('CNKI翻译', 'http://dict.cnki.net/dict_result.aspx?scw=%s', 'http://dict.cnki.net/', 'https://epub.cnki.net/favicon.ico'); const Zdic = new SiteInfo('汉典', 'https://www.zdic.net/hans/%s', 'https://www.zdic.net/', 'https://www.zdic.net/favicon.ico'); const Deepl = new SiteInfo('deepL', 'https://www.deepl.com/translator#en/zh/%s', 'https://www.deepl.com/', 'https://s2.loli.net/2022/08/17/m3H5BdLRAexbVsz.png'); const GaodeMap = new SiteInfo('高德地图', 'https://www.amap.com/search?query=%s', 'https://www.amap.com/', 'https://a.amap.com/pc/static/favicon.ico'); const SogouPic = new SiteInfo('搜狗图片', 'https://pic.sogou.com/pics?query=%s', 'https://pic.sogou.com/', 'https://dlweb.sogoucdn.com/translate/favicon.ico?v=20180424'); const BingPic = new SiteInfo('必应图片', 'https://www.bing.com/images/search?q=%s', 'https://www.bing.com/images/trending', Bing.icon); const Pixiv = new SiteInfo('pixiv', 'https://www.pixiv.net/tags/%s', 'https://www.pixiv.net/', 'https://s2.loli.net/2022/08/17/OxGZLn26TlWyQt9.png'); const Flickr = new SiteInfo('flickr', 'https://www.flickr.com/search/?q=%s', 'https://www.flickr.com/', 'https://combo.staticflickr.com/pw/favicon.ico'); const Huaban = new SiteInfo('花瓣', 'https://huaban.com/search/?q=%s', 'https://huaban.com/', 'https://huaban.com/favicon.ico'); const NeteaseMusic = new SiteInfo('网易云音乐', 'https://music.163.com/#/search/m/?s=%s', 'https://music.163.com/', 'https://s1.music.126.net/style/favicon.ico?v20180823'); const QQMusic = new SiteInfo('QQ音乐', 'https://y.qq.com/portal/search.html#page=1&searchid=1&remoteplace=txt.yqq.top&t=song&w=%s', 'https://y.qq.com/', 'https://y.qq.com/favicon.ico?max_age=2592000'); const KuwoMusic = new SiteInfo('酷我音乐', 'http://www.kuwo.cn/search/list?type=all&key=%s', 'http://www.kuwo.cn/', 'http://www.kuwo.cn/favicon.ico'); const Kugou5sing = new SiteInfo('酷狗5sing', 'http://search.5sing.kugou.com/?keyword=%s', 'http://5sing.kugou.com/index.html', 'http://5sing.kugou.com/favicon.ico'); const Dangdang = new SiteInfo('当当', 'http://search.dangdang.com/?key=%s&act=input', 'http://www.dangdang.com/', 'http://www.dangdang.com/favicon.ico'); const Suning = new SiteInfo('苏宁', 'https://search.suning.com/%s/', 'https://www.suning.com/', 'https://www.suning.com/favicon.ico'); const Amazon = new SiteInfo('亚马逊', 'https://www.amazon.cn/s?k=%s', 'https://www.amazon.cn/', 'https://www.amazon.cn/favicon.ico'); const CNKI = new SiteInfo('知网', 'http://epub.cnki.net/kns/brief/default_result.aspx?txt_1_value1=%s&dbPrefix=SCDB&db_opt=CJFQ%2CCJFN%2CCDFD%2CCMFD%2CCPFD%2CIPFD%2CCCND%2CCCJD%2CHBRD&singleDB=SCDB&action=scdbsearch', 'http://epub.cnki.net/', 'https://epub.cnki.net/favicon.ico'); const Wanfang = new SiteInfo('万方', 'http://www.wanfangdata.com.cn/search/searchList.do?searchType=all&searchWord=%s', 'http://www.wanfangdata.com.cn/', 'https://cdn.s.wanfangdata.com.cn/favicon.ico'); const WOS = new SiteInfo('WOS', 'http://apps.webofknowledge.com/UA_GeneralSearch.do?fieldCount=3&action=search&product=UA&search_mode=GeneralSearch&max_field_count=25&max_field_notice=Notice%3A+You+cannot+add+another+field.&input_invalid_notice=Search+Error%3A+Please+enter+a+search+term.&input_invalid_notice_limits=+%3Cbr%2F%3ENote%3A+Fields+displayed+in+scrolling+boxes+must+be+combined+with+at+least+one+other+search+field.&sa_img_alt=Select+terms+from+the+index&value(input1)=%s&value%28select1%29=TI&value%28hidInput1%29=initVoid&value%28hidShowIcon1%29=0&value%28bool_1_2%29=AND&value%28input2%29=&value%28select2%29=AU&value%28hidInput2%29=initAuthor&value%28hidShowIcon2%29=1&value%28bool_2_3%29=AND&value%28input3%29=&value%28select3%29=SO&value%28hidInput3%29=initSource&value%28hidShowIcon3%29=1&limitStatus=collapsed&expand_alt=Expand+these+settings&expand_title=Expand+these+settings&collapse_alt=Collapse+these+settings&collapse_title=Collapse+these+settings&SinceLastVisit_UTC=&SinceLastVisit_DATE=×panStatus=display%3A+block&timeSpanCollapsedListStatus=display%3A+none&period=Range+Selection&range=ALL&ssStatus=display%3Anone&ss_lemmatization=On&ss_query_language=&rsStatus=display%3Anone&rs_rec_per_page=10&rs_sort_by=PY.D%3BLD.D%3BVL.D%3BSO.A%3BPG.A%3BAU.A&rs_refinePanel=visibility%3Ashow', 'http://apps.webofknowledge.com/', 'https://favicon.im/clarivate.com'); const Springer = new SiteInfo('Springer', 'http://rd.springer.com/search?query=%s', 'http://rd.springer.com/', 'https://link.springer.com/oscar-static/img/favicons/darwin/favicon-de0c289efe.ico'); const Letpub = new SiteInfo('Letpub', 'https://www.letpub.com.cn/index.php?page=journalapp&view=search&searchsort=relevance&searchname=%s', 'https://www.letpub.com.cn/', 'https://www.letpub.com.cn/images/favicon.ico'); const Ablesci = new SiteInfo('科研通', 'https://www.ablesci.com/journal/index?keywords=%s', 'https://www.ablesci.com/', 'https://www.ablesci.com/favicon.ico/'); const Douban = new SiteInfo('豆瓣', 'https://www.douban.com/search?q=%s', 'https://www.douban.com/', 'https://www.douban.com/favicon.ico'); const Twitter = new SiteInfo('Twitter', 'https://twitter.com/search?q=%s', 'https://twitter.com/', 'https://s2.loli.net/2022/08/17/rsbLXJA1lG5hmfe.png'); const Facebook = new SiteInfo('Facebook', 'https://www.facebook.com/search/results.php?q=%s', 'https://www.facebook.com/', 'https://s2.loli.net/2022/08/17/69R4ObX3kUctNvM.png'); const Toutiao = new SiteInfo('今日头条', 'https://www.toutiao.com/search/?keyword=%s', 'https://www.toutiao.com/', 'https://lf3-search.searchpstatp.com/obj/card-system/favicon_5995b44.ico'); //=========================定义网站数据======================================= const defaultConf = { // // 基本配置 // showToolbar: true, // 显示划词工具条 showFrequentEngines: true, // 显示常用搜索引擎 showClassifiedEngines: true, // 显示分类搜索引擎 showPlaceholder: true, // 显示使用方式提示信息(如搜索框placeholder) enableOnInput: true, // 是否在input/textarea上启用划词和快捷键 autoCopyToClipboard: false, // 划词时自动复制到剪贴板(内容为文本格式) // // 搜索建议配置 // // 可选值baidu|google, 可根据需要调整顺序 engineSuggestions: [ { name: 'google', showCount: 5, enable: false }, { name: 'baidu', showCount: 5, enable: false }, ], // // 搜索框默认搜索引擎 // 属性: // - name 搜索引擎名称 // - url 搜索引擎搜索url // - home 搜索引擎主页url // defaultEngine: { name: Bing.name, url: Bing.url, home: Bing.home, }, // // 绑定快捷键的搜索引擎列表 // 属性: // - name 搜索引擎名称 // - url 搜索引擎搜索url // - home 搜索引擎主页url // - hotkeys 快捷键列表, 仅支持配置单字符按键的code值, 实际起作用的是Alt+单字符键, S/D/F/L键已被脚本征用 // - enable 是否启用 // hotkeyEngines: [ { name: '百度百科', url: Baidubaike.url, home: Baidubaike.home, hotkeys: ['KeyW'], enable: false, }, { name: '百度翻译', url: Baidufanyi.url, home: Baidufanyi.home, hotkeys: ['KeyE'], enable: false, }, { name: '百度', url: Baidu.url, home: Baidu.home, hotkeys: ['KeyB'], enable: false, }, { name: 'Google', url: Google.url, home: Google.home, hotkeys: ['KeyG'], enable: false, }, ], // // 常用搜索引擎列表 // 属性: // - name 搜索引擎名称 // - url 搜索引擎搜索url // - home 搜索引擎主页url // - icon 搜索引擎图标, base64编码 // - enable 是否启用 // frequentEngines: [ Baidu.callSiteInformation(), Google.callSiteInformation(), Bing.callSiteInformation(), Baidufanyi.callSiteInformation(), GitHub.callSiteInformation(false), Zhihu.callSiteInformation(), Googlenews.callSiteInformation(false), Bilibili.callSiteInformation(), Taobao.callSiteInformation(false), Jingdong.callSiteInformation(), Tianmao.callSiteInformation(false), Baiduwangpan.callSiteInformation(false), Maimai.callSiteInformation(false), ], // // 分类搜索引擎列表, 二维数组, 默认认为该配置包含了所有已配置搜索引擎 // 一级分类属性: // - name 分类名称 // - enable 该分类是否启用 // - engines 该分类下的搜索引擎列表 // 二级搜索引擎属性: // - name 搜索引擎名称 // - url 搜索引擎搜索url // - home 搜索引擎主页url // - icon 搜索引擎图标, base64编码 // - enable 搜索引擎是否启用 // classifiedEngines: [ { name: '搜索引擎', enable: true, engines: [ Baidu.callSiteInformation(), Google.callSiteInformation(), Bing.callSiteInformation(), Sougou.callSiteInformation(), So360.callSiteInformation() ] }, { name: '知识', enable: true, engines: [ Zhihu.callSiteInformation(), StackOverflow.callSiteInformation(), Maimai.callSiteInformation(), Quora.callSiteInformation(false), Baiduzhidao.callSiteInformation(), Wikipedia.callSiteInformation(), Baidubaike.callSiteInformation(), Moegirl.callSiteInformation(false), Docin.callSiteInformation(), DoubanBook.callSiteInformation(), WeixinSogou.callSiteInformation(), Guokr.callSiteInformation(false) ] }, { name: '开发', enable: true, engines: [ StackOverflow.callSiteInformation(), ApacheIssues.callSiteInformation(), GitHub.callSiteInformation(), Maven.callSiteInformation() ] }, { name: '翻译', enable: true, engines: [ Baidufanyi.callSiteInformation(), Googlefanyi.callSiteInformation(), Youdao.callSiteInformation(), Bingfanyi.callSiteInformation(), Haici.callSiteInformation(), CNKIfanyi.callSiteInformation(false), Zdic.callSiteInformation(false), Deepl.callSiteInformation() ] }, { name: '地图', enable: true, engines: [ Baidumap.callSiteInformation(), GaodeMap.callSiteInformation(), Googlemap.callSiteInformation(), Googleearth.callSiteInformation() ] }, { name: '图片', enable: true, engines: [ Baidutupian.callSiteInformation(), SogouPic.callSiteInformation(), Googlepic.callSiteInformation(), BingPic.callSiteInformation(), Pixiv.callSiteInformation(), Flickr.callSiteInformation(), Huaban.callSiteInformation() ] }, { name: '音乐', enable: true, engines: [ NeteaseMusic.callSiteInformation(), QQMusic.callSiteInformation(), KuwoMusic.callSiteInformation(), Kugou5sing.callSiteInformation() ] }, { name: '购物', enable: true, engines: [ Taobao.callSiteInformation(), Jingdong.callSiteInformation(), Tianmao.callSiteInformation(), Dangdang.callSiteInformation(false), Suning.callSiteInformation(false), Amazon.callSiteInformation(false) ] }, { name: '学术', enable: true, engines: [ Googlexueshu.callSiteInformation(), Baiduxueshu.callSiteInformation(), CNKI.callSiteInformation(), Wanfang.callSiteInformation(), WOS.callSiteInformation(), Springer.callSiteInformation(), Letpub.callSiteInformation(), Ablesci.callSiteInformation() ] }, { name: '社交', enable: true, engines: [ Weibo.callSiteInformation(), Baidutieba.callSiteInformation(), Zhihu.callSiteInformation(), Douban.callSiteInformation(), Twitter.callSiteInformation(false), Facebook.callSiteInformation(false) ] }, { name: '新闻', enable: false, engines: [ Googlenews.callSiteInformation(), Baiduxinwen.callSiteInformation(), Toutiao.callSiteInformation(), Weibo.callSiteInformation(), Zhihu.callSiteInformation() ] } ], }; /////////////////////////////////////////////////////////////////// // css样式 /////////////////////////////////////////////////////////////////// const sheet = ` /* —————— 主窗口整体美化(横向分组) —————— */ .qs-main-background-layer { all: initial !important; position: fixed !important; inset: 0 !important; background: rgba(0,0,0,0.35) !important; backdrop-filter: blur(6px) !important; z-index: 20000 !important; } .qs-mainbox { all: initial !important; position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 90vw !important; max-width: 1100px !important; max-height: 80vh !important; background: #ffffff !important; border-radius: 16px !important; box-shadow: 0 12px 32px rgba(0,0,0,0.2) !important; padding: 30px 40px 25px !important; overflow-y: auto !important; z-index: 30000 !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif !important; } /* 搜索框 */ .qs-main-search-input { all: initial !important; width: 100% !important; height: 48px !important; font-size: 18px !important; padding: 0 18px !important; border: 2px solid #e5e7eb !important; border-radius: 12px !important; outline: none !important; transition: border-color 0.2s !important; } .qs-main-search-input:focus { border-color: #3b82f6 !important; } /* 常用引擎图标 */ .qs-main-frequent-box { margin: 25px 0 15px !important; text-align: center !important; } .qs-main-frequent-icon { width: 36px !important; height: 36px !important; margin: 0 8px !important; border-radius: 8px !important; cursor: pointer !important; transition: transform 0.15s !important; } .qs-main-frequent-icon:hover { transform: scale(1.1) !important; } /* ——— 横向分组核心 ——— */ .qs-main-classified-box { display: flex !important; flex-wrap: wrap !important; gap: 30px !important; justify-content: center !important; margin-top: 25px !important; } .qs-main-classified-family-box { flex: 0 1 auto !important; min-width: 120px !important; max-width: 200px !important; } .qs-main-classified-family-title { font-size: 20px !important; font-weight: 600 !important; color: #374151 !important; margin-bottom: 8px !important; text-align: center !important; } .qs-main-classified-family-engine { display: flex !important; align-items: center !important; margin: 6px 0 !important; cursor: pointer !important; padding: 4px 6px !important; border-radius: 8px !important; transition: background 0.15s !important; } .qs-main-classified-family-engine:hover { background: #f3f4f6 !important; } .qs-main-classified-family-engine-icon { width: 20px !important; height: 20px !important; margin-right: 8px !important; } .qs-main-classified-family-engine-name { font-size: 15px !important; color: #4b5563 !important; } /* 底部菜单大字体 */ .qs-main-help-info-box { margin-top: 30px !important; text-align: center !important; font-size: 16px !important; font-weight: 500 !important; } .qs-main-help-info-item { color: #3b82f6 !important; margin: 0 12px !important; cursor: pointer !important; text-decoration: none !important; transition: color 0.2s !important; } .qs-main-help-info-item:hover { color: #1d4ed8 !important; text-decoration: underline !important; } /* —— 其余原有样式(工具条/设置/提示层等)兼容 —— */ .qs-toolbar { all: initial !important; position: absolute !important; display: block !important; height: 26px !important; padding: 2px !important; white-space: nowrap !important; border: 1px solid #F5F5F5 !important; box-shadow: 0px 0px 2px #BBB !important; background-color: #FFF !important; z-index: 10000 !important; } .qs-toolbar-icon { all: initial !important; display: inline-block !important; margin: 0px !important; padding: 2px !important; width: 20px !important; height: 20px !important; border: 1px solid #FFF !important; cursor: pointer !important; } .qs-toolbar-icon:hover { border: 1px solid #CCC !important; } /* 在原有样式基础上添加以下设置窗口样式 */ .qs-setting-box { all: initial !important; position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 80vw !important; max-width: 800px !important; max-height: 80vh !important; background: #ffffff !important; border-radius: 16px !important; box-shadow: 0 12px 32px rgba(0,0,0,0.2) !important; padding: 30px 40px 25px !important; overflow-y: auto !important; z-index: 40000 !important; /* 确保在遮罩层上方 */ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif !important; } /* 设置窗口内的标题样式 */ .qs-setting-box h3 { font-size: 24px !important; color: #374151 !important; margin: 0 0 20px !important; padding: 0 !important; } /* 设置窗口内的文本区域样式 */ .qs-setting-box textarea { width: 100% !important; min-height: 300px !important; font-size: 14px !important; /* 增大字体 */ padding: 15px !important; border: 2px solid #e5e7eb !important; border-radius: 12px !important; margin-bottom: 20px !important; resize: vertical !important; } /* 设置窗口内的按钮样式 */ .qs-setting-box button { font-size: 14px !important; padding: 10px 15px !important; margin-right: 10px !important; border-radius: 8px !important; cursor: pointer !important; border: none !important; } .qs-setting-box button:hover { opacity: 0.9 !important; } .qs-info-tips-layer, .qs-suggestions-layer { /* 保持原有定义即可 */ } `; /////////////////////////////////////////////////////////////////// // 全局变量 /////////////////////////////////////////////////////////////////// var conf = GM_getValue('qs-conf', defaultConf); var hotkey2Engine = {}; // 自定义快捷键搜索的hotkey到engine的映射表 var qsPageLock = false; // 是否在当前页面锁定快搜所有功能, 锁定之后仅响应解锁快捷键 var qsToolbar = null; // 快搜划词工具条 var qsBackgroundLayer = null; // 快搜主窗口背景层 var qsMainBox = null; // 快搜主窗口 var qsSearchInput = null; // 快搜主窗口搜索框 var qsSettingBox = null; // 快搜设置窗口 var qsConfigTextarea = null; // 快搜设置窗口配置框 var qsInfoTipsLayer = null; // 快搜信息提示浮层 var qsSuggestionsLayer = null; // 快搜搜索建议浮层 var qsSuggestionItems = []; // 快搜搜索建议所有item元素(不一定都显示) /////////////////////////////////////////////////////////////////// // 版本升级更新配置 /////////////////////////////////////////////////////////////////// // // for 1.1 -> 1.2 // if (!conf.engineSuggestions) { conf.engineSuggestions = defaultConf.engineSuggestions; GM_setValue('qs-conf', conf); } /////////////////////////////////////////////////////////////////// // 功能函数 /////////////////////////////////////////////////////////////////// // 获取元素style属性, 包括css中的 function getStyleByElement(e, styleProp) { if (window.getComputedStyle) { return document.defaultView.getComputedStyle(e, null).getPropertyValue(styleProp); } else if (e.currentStyle) { return e.currentStyle[styleProp]; } } // 计算元素在文档(页面)中的绝对位置 function getElementPosition(e) { return { top: e.getBoundingClientRect().top + window.scrollY, // 元素顶部相对于文档顶部距离 bottom: e.getBoundingClientRect().bottom + window.scrollY, // 元素底部相对于文档顶部距离 left: e.getBoundingClientRect().left + window.scrollX, // 元素左边相对于文档左侧距离 right: e.getBoundingClientRect().right + window.scrollX // 元素右边相对于文档左侧距离 }; } // 获取可视窗口在文档(页面)中的绝对位置 function getWindowPosition() { return { top: window.scrollY, bottom: window.scrollY + window.innerHeight, left: window.scrollX, right: window.scrollX + window.innerWidth }; } // 判断元素在文档(页面)中是否可见 function isVisualOnPage(ele) { if (getStyleByElement(ele, 'display') == 'none' || getStyleByElement(ele, 'visibility') == 'hidden' || getStyleByElement(ele, 'opacity') == '0') { return false; } if (getStyleByElement(ele, 'position') != 'fixed' && ele.offsetParent == null) { return false; } var elePos = getElementPosition(ele); if (elePos.bottom - elePos.top == 0 || elePos.right - elePos.left == 0 || elePos.bottom <= 0 || elePos.right <= 0) { return false; } return true; } // 获取选中文本 function getSelection() { return window.getSelection().toString().trim(); } // 获取当前页面匹配的 搜索引擎 及 其在同类别的搜索引擎列表中的索引 及 同类别的搜索引擎列表. // // TODO 目前只是简单地匹配域名, 待完善. function getMatchedEngineInfo() { var hostname = window.location.hostname; hostname = hostname.replace(/^(www\.)/, ''); // 因为想要在循环中返回最终结果, 因此不能使用forEach语法 for (var classEngines of conf.classifiedEngines) { for (var i = 0; i < classEngines.engines.length; i++) { var engine = classEngines.engines[i]; var engineHostname = new URL(engine.url).hostname; engineHostname = engineHostname.replace(/^(www\.)/, ''); if (hostname == engineHostname) { return { engine: engine, index: i, classEngines: classEngines }; } } } return null; } // 获取搜索引擎url中query的key function getUrlQueryKey(engine) { var params = new URL(engine.url).searchParams; for (var param of params) { if (param[1].includes('%s')) { return param[0]; } } return null; } // 移除url中的domain(protocol+host) function removeUrlDomain(url) { var u = new URL(url); var domain = `${u.protocol}//${u.host}`; return url.substring(domain.length); } // 获取当前页面url中的搜索词. // 返回值为经过URI解码的明文文本. // // 如果当前页面在配置的搜索引擎列表中, 尝试从url中解析参数, 分为engine.url中含有问号(?)和不含问号(?)两种情况. // 如果没有解析到或者当前页面不在配置的搜索引擎列表中, 尝试获取文本(纯数字除外)在url中完整出现的input/textarea的值. // 如果还是没有, 则认为当前页面url中没有搜索词. function getUrlQuery() { var urlTail = removeUrlDomain(window.location.href); var engineInfo = getMatchedEngineInfo(); var engine = engineInfo ? engineInfo.engine : null; // 尝试利用配置的搜索引擎信息从url中获取搜索词 if (engine && engine.url.includes('%s')) { if (engine.url.includes('?')) { // engine.url中含有问号(?) var queryKey = getUrlQueryKey(engine); var params = new URLSearchParams(window.location.search); var query = params.get(queryKey); if (query) { console.log(`Quick Search: get query by URL-KV, engine is ${engine.url}`); return query; // URLSearchParams已经decode过了 } } else { // engine.url中没有问号(?) var parts = removeUrlDomain(engine.url).split('%s'); if (parts.length == 2 && urlTail.startsWith(parts[0]) && urlTail.endsWith(parts[1])) { var query = urlTail.substring(parts[0].length, urlTail.length - parts[1].length); var index = query.search(/[\/\?\=\&\#]/); // 是否含有 / ? = & # if (index != -1) { query = query.substring(0, index); } if (query) { console.log(`Quick Search: get query by URL-PART, engine is ${engine.url}`); return decodeURIComponent(query); } } } } // 尝试获取文本(纯数字除外)在url中完整出现的input/textarea的值 var eles = document.querySelectorAll('input, textarea'); for (var ele of eles) { if (isVisualOnPage(ele) && !qsMainBox.contains(ele) && !qsSettingBox.contains(ele)) { var eleValue = ele.value.trim(); if (eleValue && !/^\d+$/.test(eleValue)) { var encodedEleValue = encodeURIComponent(eleValue); var index = urlTail.indexOf(encodedEleValue); if (index != -1) { var leftChar = urlTail[index - 1]; var rightChar = urlTail[index + encodedEleValue.length]; if ((!leftChar || /[\/\=\#]/.test(leftChar)) && (!rightChar || /[\/\?\&\#]/.test(rightChar))) { console.log(`Quick Search: get query by ${ele.tagName}[id='${ele.id}'], engine is ${engine ? engine.url : 'NULL'}`); return eleValue; } } } } } console.log(`Quick Search: query is NULL, engine is ${engine ? engine.url : 'NULL'}`); return null; } // 判断是否允许工具条 function isAllowToolbar(event) { var target = event.target; if (!conf.showToolbar || qsPageLock) { return false; } if (!conf.enableOnInput && (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA')) { return false; } if (qsMainBox && qsMainBox.contains(target) || qsSettingBox && qsSettingBox.contains(target)) { return false; } return true; } // 判断是否允许响应当前按键 // 默认只响应: 单字符的Escape / Alt+单字符 / Cmd/Ctrl+Alt+单字符 function isAllowHotkey(event) { var target = event.target; if (!qsPageLock && event.code == 'Escape') { return true; } if (!event.altKey) { return false; } if (event.shiftKey) { return false; } if (qsPageLock && event.code != 'KeyL') { return false; } if ((target.tagName == 'INPUT' || target.tagName == 'TEXTAREA') && !conf.enableOnInput) { return false; } if (qsSettingBox && qsSettingBox.contains(target)) { return false; } return true; } // 获取搜索引擎主页url function getEngineHome(engine) { if (engine.home) { return engine.home; } else { var url = new URL(engine.url); return `${url.protocol}//${url.hostname}/`; } } // 获取直达的网址. 网址优先级: 搜索框已有网址(若快搜主窗口可见) > 网页中选中网址 // 返回 网址 及 网址来源 function getUrl() { var url, source; if (isMainBoxVisual()) { url = qsSearchInput.value.trim(); source = 'mainbox'; } else { url = getSelection(); source = 'selection'; } // 补全网址 if (url && !url.includes('://')) { var dotCount = (url.match(/\./g) || []).length; if (dotCount == 0) { url = 'www.' + url + '.com'; } else if (dotCount == 1) { url = 'www.' + url; } url = 'http://' + url; } if (!url) { source = null; } return { url: url, source: source }; } // 获取搜索词. 文本优先级: 搜索框已有文本(若快搜主窗口可见) > 网页中选中文本 > 当前页面搜索词 // 返回 搜索词 及 搜索词来源 function getQuery() { var query, source; if (isMainBoxVisual()) { query = qsSearchInput.value.trim(); source = 'mainbox'; } else { query = getSelection(); source = 'selection'; if (!query) { query = getUrlQuery(); source = 'url'; } } if (!query) { source = null; } return { query: query, source: source }; } // 打开url. // 当按下Cmd(Mac系统)/Ctrl(Windows/Linux系统), 则后台打开url. function openUrl(url, event) { // console.log(`Quick Search: open url, url is ${url}`); if (!url) return; if (event.metaKey || event.ctrlKey) { GM_openInTab(url, true); } else { GM_openInTab(url, false); } } // 打开engine搜索结果或engine主页. function openEngine(engine, query, event) { // console.log(`Quick Search: open engine, engine is ${engine.url}, query is ${query}`); if (!engine) return; if (query) { var url = engine.url.replace('%s', encodeURIComponent(query)); openUrl(url, event); } else { openUrl(getEngineHome(engine), event); } } // 快捷键搜索. // 同样, 当query为空时打开引擎主页, 否则正常搜索. function openEngineOnKey(engine, query, event) { openEngine(engine, query, event); } // 点击搜索引擎. // 当按下Alt, 则忽略查询词打开引擎主页, 否则正常搜索. function openEngineOnClick(engine, query, event) { if (event.altKey) { openEngine(engine, null, event); } else { openEngine(engine, query, event); } } // 点击划词工具条搜索引擎. function openEngineOnClickToolbar(engine, event) { var query = getSelection(); openEngineOnClick(engine, query, event); } // 点击快搜主窗口搜索引擎. function openEngineOnClickMainBox(engine, event) { var query = qsSearchInput.value.trim(); openEngineOnClick(engine, query, event); } // 切换快搜page lock状态 function toggleQuickSearchPageLock() { qsPageLock = qsPageLock ? false : true; if (qsPageLock) { hideToolbar(); hideMainBox(); showInfoTipsLayer('已禁用(🔒)'); } else { showInfoTipsLayer('已启用(🚀)'); } } /////////////////////////////////////////////////////////////////// // 元素创建 与 元素事件响应 /////////////////////////////////////////////////////////////////// // 加载css样式 function loadSheet() { var css = document.createElement('style'); css.type = 'text/css'; css.id = 'qs-css'; css.textContent = sheet; document.getElementsByTagName('head')[0].appendChild(css); } // 初始化热键映射表 function initHotkeyEngineMapping() { conf.hotkeyEngines.forEach(engine => { if (!engine.enable) return; // 此处return搭配forEach, 请勿改为其他形式循环 engine.hotkeys.forEach(key => { hotkey2Engine[key] = engine; }); }); } // // 划词工具条 // // 创建划词工具条 function createToolbar() { // 工具条container var toolbar = document.createElement('div'); toolbar.id = 'qs-toolbar'; toolbar.className = 'qs-toolbar'; toolbar.style.setProperty('display', 'none', 'important'); document.body.appendChild(toolbar); // 常用搜索引擎按钮 conf.frequentEngines.forEach((engine, index) => { if (!engine.enable) return; // 此处return搭配forEach, 请勿改为其他形式循环 var icon = document.createElement('img'); icon.id = 'qs-toolbar-icon-' + index; icon.className = 'qs-toolbar-icon'; icon.src = engine.icon; icon.addEventListener('click', function (e) { openEngineOnClickToolbar(engine, e); }, false); toolbar.appendChild(icon); }); // 直达网址按钮 var icon = document.createElement('img'); icon.id = 'qs-toolbar-icon-url'; icon.className = 'qs-toolbar-icon'; icon.src = 'https://s2.loli.net/2025/08/20/UoYtpzmnkqrSKd6.png'; icon.addEventListener('click', function (e) { openUrl(getUrl().url, e); }, false); toolbar.appendChild(icon); // 更多按钮 var icon = document.createElement('img'); icon.id = 'qs-toolbar-icon-more'; icon.className = 'qs-toolbar-icon'; icon.src = 'https://s2.loli.net/2025/08/20/r4QaEZVqn8O3lwX.png'; icon.addEventListener('click', function (e) { showMainBox(); hideToolbar(); }, false); toolbar.appendChild(icon); qsToolbar = toolbar; } // 划词工具条是否处于显示状态 function isToolbarVisual() { return qsToolbar && qsToolbar.style.display == 'block'; } // 显示划词工具条 function showToolbar(event) { ensureQuickSearchAlive(); if (!qsToolbar || isToolbarVisual()) { return; } var toolbar = qsToolbar; toolbar.style.setProperty('top', '-10000px', 'important'); toolbar.style.setProperty('left', '-10000px', 'important'); toolbar.style.setProperty('display', 'block', 'important'); var toolbarClientRect = toolbar.getBoundingClientRect(); var toolbarWidth = toolbarClientRect.right - toolbarClientRect.left; var toolbarHeight = toolbarClientRect.bottom - toolbarClientRect.top; var toolbarNewTop = event.pageY + 15; var toolbarNewLeft = event.pageX - (toolbarWidth / 2); var windowPos = getWindowPosition(); if (toolbarNewTop + toolbarHeight > windowPos.bottom) { toolbarNewTop = event.pageY - toolbarHeight - 15; } if (toolbarNewLeft < windowPos.left) { toolbarNewLeft = windowPos.left; } else if (toolbarNewLeft + toolbarWidth > windowPos.right) { toolbarNewLeft = windowPos.right - toolbarWidth; } toolbar.style.setProperty('top', toolbarNewTop + 'px', 'important'); toolbar.style.setProperty('left', toolbarNewLeft + 'px', 'important'); } // 隐藏划词工具条 function hideToolbar() { if (qsToolbar) { qsToolbar.style.setProperty('display', 'none', 'important'); } } // // 快搜主窗口 // // 创建快搜主窗口 function createMainBox() { // 快搜主窗口背景层 // // 随快搜主窗口一同显示/隐藏, 铺满整个可视窗口. 其作用主要是: // 1. 想要实现点击快搜主窗口外面就隐藏快搜主窗口, 但如果点击target是页面中的cross-domain iframe的话, // 当前window就不能捕获到该iframe的click事件, 所以覆盖一层作为以便捕获点击事件. // 2. 也可以做背景虚化/遮罩效果. var backgroundLayer = document.createElement('div'); backgroundLayer.id = 'qs-main-background-layer'; backgroundLayer.className = 'qs-main-background-layer'; backgroundLayer.addEventListener('click', hideMainBox, false); // ✅ 新增这行 backgroundLayer.style.setProperty('display', 'none', 'important'); document.body.appendChild(backgroundLayer); // 快搜主窗口container var mainBox = document.createElement('div'); mainBox.id = 'qs-mainbox'; mainBox.className = 'qs-mainbox'; mainBox.style.setProperty('display', 'none', 'important'); document.body.appendChild(mainBox); // 搜索框 var searchBox = document.createElement('div'); searchBox.id = 'qs-main-search-box'; searchBox.className = 'qs-main-search-box'; mainBox.appendChild(searchBox); var searchInput = document.createElement('input'); searchInput.id = 'qs-main-search-input'; searchInput.className = 'qs-main-search-input'; searchInput.addEventListener('keydown', function (e) { if (e.code == 'Enter') { // 回车键 openEngineOnClickMainBox(conf.defaultEngine, e); } }, false); if (conf.showPlaceholder) { searchInput.placeholder = `回车以使用${conf.defaultEngine.name}搜索`; } searchBox.appendChild(searchInput); // 常用搜索引擎 if (conf.showFrequentEngines) { var frequentBox = document.createElement('div'); frequentBox.id = 'qs-main-frequent-box'; frequentBox.className = 'qs-main-frequent-box'; mainBox.appendChild(frequentBox); conf.frequentEngines.forEach((engine, index) => { if (!engine.enable) return; // 此处return搭配forEach, 请勿改为其他形式循环 var icon = document.createElement('img'); icon.id = 'qs-main-frequent-icon-' + index; icon.className = 'qs-main-frequent-icon'; icon.src = engine.icon; icon.addEventListener('click', function (e) { openEngineOnClickMainBox(engine, e); }, false); frequentBox.appendChild(icon); }); } // 分类搜索引擎 if (conf.showClassifiedEngines) { var classifiedBox = document.createElement('div'); classifiedBox.id = 'qs-main-classified-box'; classifiedBox.className = 'qs-main-classified-box'; mainBox.appendChild(classifiedBox); conf.classifiedEngines.forEach((family, fIndex) => { if (!family.enable) return; // 此处return搭配forEach, 请勿改为其他形式循环 // 一个分类一列 var familyBox = document.createElement('div'); familyBox.id = 'qs-main-classified-family-box-' + fIndex; familyBox.className = 'qs-main-classified-family-box'; classifiedBox.appendChild(familyBox); // 分类标题 var familyTitle = document.createElement('div'); familyTitle.id = 'qs-main-classified-family-title-' + fIndex; familyTitle.className = 'qs-main-classified-family-title'; familyTitle.textContent = family.name; familyBox.appendChild(familyTitle); family.engines.forEach((engine, eIndex) => { if (!engine.enable) return; // 此处return搭配forEach, 请勿改为其他形式循环 // 搜索引擎 var engineBox = document.createElement('div'); engineBox.id = 'qs-main-classified-family-engine-' + fIndex + '-' + eIndex; engineBox.className = 'qs-main-classified-family-engine'; engineBox.addEventListener('click', function (e) { openEngineOnClickMainBox(engine, e); }, false); familyBox.appendChild(engineBox); // 搜索引擎icon var engineIcon = document.createElement('img'); engineIcon.id = 'qs-main-classified-family-engine-icon-' + fIndex + '-' + eIndex; engineIcon.className = 'qs-main-classified-family-engine-icon'; engineIcon.src = engine.icon; engineBox.appendChild(engineIcon); // 搜索引擎name var engineName = document.createElement('span'); engineName.id = 'qs-main-classified-family-engine-name-' + fIndex + '-' + eIndex; engineName.className = 'qs-main-classified-family-engine-name'; engineName.textContent = engine.name; engineBox.appendChild(engineName); }); }); } // 帮助信息 var helpInfoBox = document.createElement('div'); helpInfoBox.id = 'qs-main-help-info-box'; helpInfoBox.className = 'qs-main-help-info-box'; mainBox.appendChild(helpInfoBox); // 主页 var homeLink = document.createElement('a'); homeLink.id = 'qs-main-help-info-home'; homeLink.className = 'qs-main-help-info-item'; homeLink.textContent = '主页'; homeLink.href = 'https://github.com/eyinwei/Quick_Search'; homeLink.target = '_blank'; helpInfoBox.appendChild(homeLink); // 设置 var settingLink = document.createElement('a'); settingLink.id = 'qs-main-help-info-setting'; settingLink.className = 'qs-main-help-info-item'; settingLink.textContent = '设置'; settingLink.onclick = function (e) { showSettingBox(); }; helpInfoBox.appendChild(settingLink); // ✅ 新增:工具条设置按钮 var toolbarSettingLink = document.createElement('a'); toolbarSettingLink.id = 'qs-main-help-info-toolbar-setting'; toolbarSettingLink.className = 'qs-main-help-info-item'; toolbarSettingLink.textContent = '工具条设置'; toolbarSettingLink.onclick = function (e) { showToolbarEditor(); }; helpInfoBox.appendChild(toolbarSettingLink); qsBackgroundLayer = backgroundLayer; qsMainBox = mainBox; qsSearchInput = searchInput; } // 快搜主窗口是否处于显示状态 function isMainBoxVisual() { return qsMainBox.style.display == 'block'; } // 显示快搜主窗口. // 可选输入text为没有经过URI编码的明文文本, 该参数一般在iframe发来消息时使用. // 快搜主窗口中搜索框的文本优先级: 参数指定文本 > 网页选中文本 > 搜索框已有文本 > 当前页面搜索词 function showMainBox(text) { ensureQuickSearchAlive(); // 快搜主窗口在iframe中不显示 if (isMainBoxVisual() || window.self != window.top) { return; } // 设置搜索框内容 var query = text; if (!query) { query = getSelection(); } if (!query) { query = qsSearchInput.value.trim(); } if (!query) { query = getUrlQuery(); } qsSearchInput.value = query; qsBackgroundLayer.style.setProperty('display', 'block', 'important'); qsMainBox.style.setProperty('display', 'block', 'important'); // 选中搜索框文本 qsSearchInput.select(); // 隐藏划词工具条 if (isToolbarVisual()) { hideToolbar(); } } // 隐藏快搜主窗口 function hideMainBox() { destroySuggestions(); qsBackgroundLayer.style.setProperty('display', 'none', 'important'); qsMainBox.style.setProperty('display', 'none', 'important'); } // // 设置窗口 // // 创建设置窗口 function createSettingBox() { // 设置窗口container var settingBox = document.createElement('div'); settingBox.id = 'qs-setting-box'; settingBox.className = 'qs-setting-box'; settingBox.style.setProperty('display', 'none', 'important'); document.body.appendChild(settingBox); // 配置文本框 var configTextarea = document.createElement('textarea'); configTextarea.id = 'qs-setting-config-textarea'; configTextarea.className = 'qs-setting-config-textarea'; settingBox.appendChild(configTextarea); // 底部按钮container var buttonBar = document.createElement('div'); buttonBar.id = 'qs-setting-button-bar'; buttonBar.className = 'qs-setting-button-bar'; settingBox.appendChild(buttonBar); // 重置按钮 var resetButton = document.createElement('button'); resetButton.id = 'qs-setting-button-reset'; resetButton.className = 'qs-setting-button'; resetButton.type = 'button'; resetButton.textContent = '重置'; resetButton.onclick = function (e) { configTextarea.value = JSON.stringify(defaultConf, null, 4); } buttonBar.appendChild(resetButton); // 关闭按钮 var closeButton = document.createElement('button'); closeButton.id = 'qs-setting-button-close'; closeButton.className = 'qs-setting-button'; closeButton.type = 'button'; closeButton.textContent = '取消'; closeButton.onclick = function (e) { hideSettingBox(); } buttonBar.appendChild(closeButton); // 保存按钮 var saveButton = document.createElement('button'); saveButton.id = 'qs-setting-button-save'; saveButton.className = 'qs-setting-button'; saveButton.type = 'button'; saveButton.textContent = '保存'; saveButton.onclick = function (e) { var newConf = JSON.parse(configTextarea.value); GM_setValue('qs-conf', newConf); hideSettingBox(); // 需用户手动刷新页面重新加载配置使其生效 alert('设置已保存, 刷新页面后生效.'); } buttonBar.appendChild(saveButton); qsSettingBox = settingBox; qsConfigTextarea = configTextarea; } // 设置窗口是否处于显示状态 function isSettingBoxVisual() { return qsSettingBox.style.display == 'block'; } // 显示设置窗口 function showSettingBox() { ensureQuickSearchAlive(); if (isSettingBoxVisual()) { return; } var confStr = JSON.stringify(conf, null, 4); qsConfigTextarea.value = confStr; qsSettingBox.style.setProperty('display', 'block', 'important'); } // 隐藏设置窗口 function hideSettingBox() { qsSettingBox.style.setProperty('display', 'none', 'important'); } // // 信息提示浮层 // // 创建信息提示浮层 function createInfoTipsLayer() { var infoTipsLayer = document.createElement('div'); infoTipsLayer.id = 'qs-info-tips-layer'; infoTipsLayer.className = 'qs-info-tips-layer'; infoTipsLayer.style.setProperty('display', 'none', 'important'); document.body.appendChild(infoTipsLayer); qsInfoTipsLayer = infoTipsLayer; } // 显示信息提示浮层 var idOfSettimeout = null; function showInfoTipsLayer(info) { qsInfoTipsLayer.textContent = 'Quick Search: ' + info; qsInfoTipsLayer.style.setProperty('display', 'block', 'important'); if (idOfSettimeout) { clearTimeout(idOfSettimeout); } idOfSettimeout = setTimeout(function () { qsInfoTipsLayer.style.setProperty('display', 'none', 'important'); }, 2000); } // // 搜索建议 // var rawInputQuery = null; // 输入框中的原始查询 var multiEngineSuggestions = []; // 多个搜索引擎的建议, 每个一个子数组 var suggestionCount = 0; // 多个搜索引擎的实际建议的总数 var selectedSuggestionIndex = -1; // 用户选中的搜索建议项对应的index // 搜索建议最大条数, 用于事先创建好相应元素 function getMaxSuggestionCount() { var count = 0; conf.engineSuggestions.forEach(es => { if (!es.enable) return; count += es.showCount; }); return count; } // 创建搜索建议浮层 function createSuggestionsLayer() { var maxSuggestionCount = getMaxSuggestionCount(); if (maxSuggestionCount == 0) { return; } // 搜索建议浮层 var suggestionsLayer = document.createElement('div'); suggestionsLayer.id = 'qs-suggestions-layer'; suggestionsLayer.className = 'qs-suggestions-layer'; suggestionsLayer.style.setProperty('display', 'none', 'important'); document.body.appendChild(suggestionsLayer); // 搜索建议条目 for (var i = 0; i < maxSuggestionCount; i++) { var suggestionItem = document.createElement('div'); suggestionItem.id = 'qs-suggestion-item-' + i; suggestionItem.className = 'qs-suggestion-item'; suggestionItem.addEventListener('click', function (e) { qsSearchInput.value = this.textContent; openEngineOnClickMainBox(conf.defaultEngine, e); }, true); suggestionsLayer.appendChild(suggestionItem); qsSuggestionItems.push(suggestionItem); } // 向搜索框添加响应函数 qsSearchInput.addEventListener('input', function (e) { var query = qsSearchInput.value.trim(); if (query) { triggerSuggestions(query); } else { destroySuggestions(); } }, true); qsSearchInput.addEventListener('mousedown', function (e) { var query = qsSearchInput.value.trim(); if (query) { triggerSuggestions(query); } }, true); qsSearchInput.addEventListener('keydown', function (e) { if (e.code == 'ArrowDown' && isSuggestionsLayerVisual()) { e.preventDefault(); selectSuggestionItem(selectedSuggestionIndex + 1); return; } if (e.code == 'ArrowUp' && isSuggestionsLayerVisual()) { e.preventDefault(); selectSuggestionItem(selectedSuggestionIndex - 1); return; } if (e.code == 'Tab' && isSuggestionsLayerVisual()) { e.preventDefault(); if (e.shiftKey) { selectSuggestionItem(selectedSuggestionIndex - 1); } else { selectSuggestionItem(selectedSuggestionIndex + 1); } return; } }, true); qsSuggestionsLayer = suggestionsLayer; } // 判断搜索建议浮层是否显示 function isSuggestionsLayerVisual() { return qsSuggestionsLayer && qsSuggestionsLayer.style.display == 'block'; } // 显示搜索建议浮层 function _showSuggestionsLayer() { if (!qsSuggestionsLayer || isSuggestionsLayerVisual()) { return; } var inputPos = qsSearchInput.getBoundingClientRect(); var top = inputPos.bottom + 'px'; var left = inputPos.left + 'px'; var width = (inputPos.right - inputPos.left) + 'px'; qsSuggestionsLayer.style.setProperty('top', top, 'important'); qsSuggestionsLayer.style.setProperty('left', left, 'important'); qsSuggestionsLayer.style.setProperty('width', width, 'important'); qsSuggestionsLayer.style.setProperty('display', 'block', 'important'); } // 隐藏搜索建议浮层 function _hideSuggestionsLayer() { if (qsSuggestionsLayer) { qsSuggestionsLayer.style.setProperty('display', 'none', 'important'); } } // // 请求百度搜索建议 // const baiduSuggestionUrl = { 'http:': 'http://suggestion.baidu.com/su?wd=%s&cb=callback', 'https:': 'https://suggestion.baidu.com/su?wd=%s&cb=callback', }[window.location.protocol]; // 油猴脚本的GM_xmlhttpRequest可以直接跨域请求, 不受同源策略限制, 这样就不用通过jQuery来发送jsonp请求了. function requestBaiduSuggestions(query, index, count) { function callback(res) { multiEngineSuggestions[index] = res.s.slice(0, count); loadSuggestions(); } var url = baiduSuggestionUrl.replace('%s', encodeURIComponent(query)); GM_xmlhttpRequest({ method: 'GET', url: url, timeout: 3000, onload: response => { if (response.status == 200) { eval(response.responseText); } else { console.log(`Quick Search: Baidu Suggestions: code ${response.status}`); } } }); } // // 请求Google搜索建议 // const googleSuggestionUrl = { 'http:': 'http://suggestqueries.google.com/complete/search?client=firefox&q=%s&jsonp=callback', 'https:': 'https://suggestqueries.google.com/complete/search?client=firefox&q=%s&jsonp=callback', }[window.location.protocol]; function requestGoogleSuggestions(query, index, count) { function callback(res) { multiEngineSuggestions[index] = res[1].slice(0, count); loadSuggestions(); } var url = googleSuggestionUrl.replace('%s', encodeURIComponent(query)); GM_xmlhttpRequest({ method: 'GET', url: url, timeout: 3000, onload: response => { if (response.status == 200) { eval(response.responseText); } else { console.log(`Quick Search: Google Suggestions: code ${response.status}`); } } }); } var suggestionHandlers = { 'baidu': requestBaiduSuggestions, 'google': requestGoogleSuggestions, } // 选中搜索建议项 function selectSuggestionItem(newSelectedIndex) { if (qsSuggestionItems[selectedSuggestionIndex]) { qsSuggestionItems[selectedSuggestionIndex].className = 'qs-suggestion-item'; } selectedSuggestionIndex = newSelectedIndex; selectedSuggestionIndex = selectedSuggestionIndex < -1 ? suggestionCount - 1 : selectedSuggestionIndex; selectedSuggestionIndex = selectedSuggestionIndex >= suggestionCount ? -1 : selectedSuggestionIndex; if (selectedSuggestionIndex == -1) { qsSearchInput.value = rawInputQuery; } else { qsSearchInput.value = qsSuggestionItems[selectedSuggestionIndex].textContent; qsSuggestionItems[selectedSuggestionIndex].className = 'qs-suggestion-item-selected'; } } // 触发搜索建议 function triggerSuggestions(query) { rawInputQuery = query; if (selectedSuggestionIndex != -1) { selectSuggestionItem(-1); } var index = 0; conf.engineSuggestions.forEach(es => { if (!es.enable) return; suggestionHandlers[es.name](query, index, es.showCount); index++; }) } // 装载搜索建议 function loadSuggestions() { // 由于装载是异步延迟的, 若用户已经删光了输入框内容, 则不显示搜索建议 if (!qsSearchInput.value.trim()) { destroySuggestions(); return; } // 多个搜索引擎的建议合并并去重 var allSuggestions = multiEngineSuggestions.flat(1).filter((x, i, a) => a.indexOf(x) == i); suggestionCount = allSuggestions.length; allSuggestions.forEach((suggestion, i) => { qsSuggestionItems[i].textContent = suggestion; qsSuggestionItems[i].style.setProperty('display', 'block', 'important'); }); for (var i = allSuggestions.length; i < qsSuggestionItems.length; i++) { qsSuggestionItems[i].style.setProperty('display', 'none', 'important'); } if (!isSuggestionsLayerVisual()) { _showSuggestionsLayer(); } } // 销毁搜索建议 function destroySuggestions() { _hideSuggestionsLayer(); rawInputQuery = null; multiEngineSuggestions = []; suggestionCount = 0; selectedSuggestionIndex = -1; } // // 创建以上所有东东 // function initQuickSearch() { loadSheet(); initHotkeyEngineMapping(); if (conf.showToolbar) { createToolbar(); } createMainBox(); createSettingBox(); createInfoTipsLayer(); createSuggestionsLayer(); } // 百度等网页会在不刷新页面的情况下改变网页内容, 导致quick search除了js脚本之外的东东全部没了. // 此函数用于确保quick search处于可用状态, 需在toolbar或mainbox等窗口每次显示时调用. function ensureQuickSearchAlive() { var css = document.querySelector('#qs-css'); var mainbox = document.querySelector('#qs-mainbox'); if (!css || !mainbox) { initQuickSearch(); } } // ✅ 工具条设置弹窗 // ❗️ 仅定义逻辑,不立即创建 DOM function showToolbarEditor() { // 1. 先移除旧弹窗(如果已存在) const old = document.getElementById('qs-toolbar-editor'); if (old) old.remove(); // 2. 创建并插入 DOM const box = document.createElement('div'); box.id = 'qs-toolbar-editor'; box.className = 'qs-setting-box'; box.style.cssText = ` position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 300px !important; background: #fff !important; border: 1px solid #ccc !important; box-shadow: 0 0 10px rgba(0,0,0,0.3) !important; padding: 15px !important; z-index: 99999 !important; font-family: Arial, sans-serif !important; font-size: 14px !important; `; document.body.appendChild(box); // 3. 标题 const title = document.createElement('div'); title.textContent = '编辑工具条网站'; title.style.cssText = 'font-weight: bold; margin-bottom: 10px;'; box.appendChild(title); // 4. 网站列表 const list = document.createElement('div'); box.appendChild(list); conf.frequentEngines.forEach((engine, i) => { const label = document.createElement('label'); label.style.display = 'block'; label.style.margin = '4px 0'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = engine.enable; checkbox.onchange = () => { conf.frequentEngines[i].enable = checkbox.checked; }; label.appendChild(checkbox); label.appendChild(document.createTextNode(engine.name)); list.appendChild(label); }); // 5. 按钮区 const addBtn = document.createElement('button'); addBtn.textContent = '+ 添加网站'; addBtn.onclick = () => { const name = prompt('网站名称:'); const url = prompt('搜索URL(用 %s 代替关键词):'); const icon = prompt('图标URL(如 favicon.ico):'); if (name && url && icon) { conf.frequentEngines.push({ name, url, icon, home: '', enable: true }); showToolbarEditor(); // 重新渲染 } }; box.appendChild(addBtn); const saveBtn = document.createElement('button'); saveBtn.textContent = '保存'; saveBtn.style.marginLeft = '10px'; saveBtn.onclick = () => { GM_setValue('qs-conf', conf); if (qsToolbar) { qsToolbar.remove(); createToolbar(); } box.remove(); // 关闭弹窗 }; box.appendChild(saveBtn); const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.marginLeft = '10px'; cancelBtn.onclick = () => box.remove(); // 关闭弹窗 box.appendChild(cancelBtn); } //GM_setValue('qs-conf', defaultConf); // 🔥 强制重置配置 initQuickSearch(); // ✅ 全局调用:仅由菜单按钮触发 window.showToolbarEditor = showToolbarEditor; /////////////////////////////////////////////////////////////////// // 全局事件响应 // // 我们将全局事件绑定在捕获阶段执行, 避免事件响应被网页自带的脚本拦截掉. /////////////////////////////////////////////////////////////////// // // top window和iframe共用的事件处理逻辑 // window.addEventListener('mousedown', function (e) { var target = e.target; // 隐藏工具条 if (isToolbarVisual() && !qsToolbar.contains(target)) { hideToolbar(); } }, true); window.addEventListener('mouseup', function (e) { var target = e.target; // 显示/隐藏工具条 if (isAllowToolbar(e)) { var selection = getSelection(); if (selection && !isToolbarVisual()) { showToolbar(e); } if (!selection && isToolbarVisual()) { hideToolbar(); } } // 划词时自动复制文本到剪贴板 if (conf.autoCopyToClipboard && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA' && !qsMainBox.contains(target) && !qsSettingBox.contains(target)) { var selection = getSelection(); if (selection) { GM_setClipboard(selection, 'text/plain'); } } }, true); // 有时候selectionchange发生在mouseup之后, 导致没有selection时toolbar依然显示. // 故再添加selectionchange事件以隐藏toolbar. // 由于在鼠标划词拖动过程中会不停触发selectionchange事件, 所以最好不要以此事件来显示/调整toolbar位置. document.addEventListener('selectionchange', function (e) { var selection = getSelection(); if (!selection && isToolbarVisual()) { hideToolbar(); } }, true); window.addEventListener('keydown', function (e) { if (!isAllowHotkey(e)) { return; } // Alt+S键, 超级快搜. 优先级如下: // 1. 快搜主窗口可见, 使用默认搜索引擎搜索搜索框文本. // 2. 网页有选中文本, 使用默认搜索引擎搜索文本. // 3. 当前页面url中有搜索词, 挑选当前搜索引擎分类中的另一个搜索引擎搜索该词. // 4. 都没有则打开快搜主窗口. if (e.code == 'KeyS') { e.preventDefault(); var engine = null; var query = getQuery(); if (query.source == 'mainbox' || query.source == 'selection') { engine = conf.defaultEngine; } else if (query.source == 'url') { var nowEngineInfo = getMatchedEngineInfo(); if (nowEngineInfo) { var nowClassEngines = nowEngineInfo.classEngines.engines; nowClassEngines.forEach((eng, i) => { if (!engine && eng.enable && i != nowEngineInfo.index) { engine = eng; } }); } } if (engine && query.query) { openEngineOnKey(engine, query.query, e); } else if (!isMainBoxVisual()) { showMainBox(); } else { hideMainBox(); } return; } // Alt+D键, 网址直达. 网址优先级: 搜索框已有网址(若快搜主窗口可见) > 网页中选中网址 if (e.code == 'KeyD') { e.preventDefault(); openUrl(getUrl().url, e); return; } // Alt+自定义快捷键搜索. 文本优先级: 搜索框已有文本(若快搜主窗口可见) > 网页中选中文本 > 当前页面搜索词 if (hotkey2Engine[e.code]) { e.preventDefault(); var engine = hotkey2Engine[e.code]; var query = getQuery(); openEngineOnKey(engine, query.query, e); return; } }, true); // // 只在top window中使用的事件处理逻辑 // if (window.self == window.top) { window.addEventListener('mousedown', function (e) { var target = e.target; // 隐藏快搜主窗口 if (isMainBoxVisual() && !isSettingBoxVisual() && !qsMainBox.contains(target) && !qsSuggestionsLayer.contains(target)) { hideMainBox(); } // 隐藏搜索建议浮层 if (isSuggestionsLayerVisual() && !qsSuggestionsLayer.contains(target) && !qsSearchInput.contains(target)) { destroySuggestions(); } }, true); window.addEventListener('keydown', function (e) { if (!isAllowHotkey(e)) { return; } // Alt+F键, 显示/隐藏快搜主窗口 if (e.code == 'KeyF') { e.preventDefault(); if (!isMainBoxVisual()) { showMainBox(); } else { hideMainBox(); } return; } // Esc键, 隐藏快搜主窗口 if (e.code == 'Escape') { if (isMainBoxVisual() && !isSettingBoxVisual()) { hideMainBox(); } return; } // Alt+L键, 锁定/解锁快搜所有功能. if (e.code == 'KeyL') { e.preventDefault(); toggleQuickSearchPageLock(); return; } }, true); // 处理iframe发来的消息 window.addEventListener('message', function (e) { if (e.data.source != 'qs-iframe') { return; } if (e.data.keydown) { if (e.data.keydown == 'Alt+KeyF') { if (!qsPageLock) { if (!isMainBoxVisual()) { showMainBox(e.data.query); } else { hideMainBox(); } } } if (e.data.keydown == 'Alt+KeyL') { toggleQuickSearchPageLock(); } } }, true); } // // 只在iframe中使用的事件处理逻辑 // if (window.self != window.top) { // 向top窗口发送消息 window.addEventListener('keydown', function (e) { if (e.altKey && e.code == 'KeyF') { var query = getSelection(); // 跨域iframe中不能执行window.top.origin, 故此处使用* window.top.postMessage({ source: 'qs-iframe', keydown: 'Alt+KeyF', query: query }, '*'); } if (e.altKey && e.code == 'KeyL') { window.top.postMessage({ source: 'qs-iframe', keydown: 'Alt+KeyL' }, '*'); } }, true); } })();