Search Cross

不同搜索引擎间的切换,自用

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Search Cross
// @namespace    https://github.com/saplf/search-cross
// @version      0.8
// @description  不同搜索引擎间的切换,自用
// @author       saplf
// @license      GPL-3.0
// @supportURL   https://github.com/saplf/search-cross
// @home-url     https://greasyfork.org/zh-CN/scripts/389989-search-cross
// @match        *://www.baidu.com/s?*
// @match        *://www.google.com/search?*
// @match        *://www.bing.com/search?*
// @match        *://www.so.com/s?*
// @match        *://github.com/search?*
// @match        *://www.zhihu.com/search?*
// @match        *://search.bilibili.com/*
// @match        *://zh.wikipedia.org/wiki/*
// @match        *://www.sogou.com/web?*
// @match        *://www.douban.com/search?*
// @match        *://mijisou.com/?*
// @match        *://duckduckgo.com/?*
// @match        *://s.taobao.com/search?*
// @note         2020.01.10-v0.3 修复github下样式问题
// @note         2020.06.29-v0.4 切换图标源,减小源码体积;添加中文维基
// @note         2020.06.29-v0.5 由于 Github 的安全策略,外部样式代码改由代码下载
// @note         2020.06.29-v0.6 添加部分搜索引擎
// @note         2020.07.27-v0.7 添加部分搜索引擎;添加设置面板
// @note         2020.09.28-v0.8 修复 Safari 浏览器不支持反向预查正则的 bug
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// @connect      at.alicdn.com
// @run-at       document-end
// ==/UserScript==

var config = {
    default: {
        position: 'left', // 'left' or 'right'
        height: 54,
        top: '120px',
        peekSize: 30,
        delayEnter: 120,
        delayLeave: 400,
        zIndex: 9999,
        triggleVer: '10px',
        triggleHor: '20px',
    },
    'www.google.com': {
        top: '140px',
    },
};

var engines = {
    'www.baidu.com': {
        name: '百度',
        icon: 'sc-baidu',
        url: 'https://www.baidu.com/s?wd={q}',
        match: /(?:wd=)([^&]+)(?=&|$)/,
    },
    'www.google.com': {
        name: 'Google',
        icon: 'sc-google',
        url: 'https://www.google.com/search?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    'www.bing.com': {
        name: 'Bing',
        icon: 'sc-bing',
        url: 'https://cn.bing.com/search?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    'github.com': {
        name: 'GitHub',
        icon: 'sc-github',
        url: 'https://github.com/search?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    'www.zhihu.com': {
        name: '知乎',
        icon: 'sc-zhihu',
        url: 'https://www.zhihu.com/search?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    'search.bilibili.com': {
        name: 'bilibili',
        icon: 'sc-bilibili',
        url: 'https://search.bilibili.com/all?keyword={q}',
        match: /(?:keyword=)([^&]+)(?=&|$)/,
    },
    'zh.wikipedia.org': {
        name: '维基中文',
        icon: 'sc-wiki',
        url: 'https://zh.wikipedia.org/wiki/{q}',
        match: /(?:wiki\/)([^?&]+)(?=&|$|\?)/,
    },
    'www.so.com': {
        name: '360',
        icon: 'sc-360',
        url: 'https://www.so.com/s?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    'www.sogou.com': {
        name: '搜狗',
        icon: 'sc-sougou',
        url: 'https://www.sogou.com/web?query={q}',
        match: /(?:query=)([^&]+)(?=&|$)/,
    },
    'www.douban.com': {
        name: '豆瓣',
        icon: 'sc-douban',
        url: 'https://www.douban.com/search?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    'mijisou.com': {
        name: '秘迹',
        icon: 'sc-mj',
        url: 'https://mijisou.com/?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    'duckduckgo.com': {
        name: 'Duck',
        icon: 'sc-ddg',
        url: 'https://duckduckgo.com/?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
    's.taobao.com': {
        name: '淘宝',
        icon: 'sc-taobao',
        url: 'https://s.taobao.com/search?q={q}',
        match: /(?:q=)([^&]+)(?=&|$)/,
    },
};
var enginesArray = Object.entries(engines);

// var configCached = GM_getValue('config', config);
// GM_setValue('config', configCached);
// var setting = Object.assign(config.default, configCached.default, configCached[location.host]);
// engines = GM_getValue('sites', engines);
// GM_setValue('sites', engines);
var setting = config.default;

// 标记用于重置存储数据的计数量
var appClearCounter = 1;
var cacheClearCounter = GM_getValue('cacheClearCounter', 0);
var enabledSites;
if (cacheClearCounter < appClearCounter) {
    GM_setValue('cacheClearCounter', appClearCounter);
    enabledSites = enginesArray.map(function (it) { return it[0] });
} else {
    enabledSites = GM_getValue('enabledSites', enginesArray.map(function (it) { return it[0] }));
}

function appendStyles() {
    var isLeft = setting.position === 'left';
    var offsetSignal = isLeft ? '-' : '';
    GM_addStyle(`
#sc-panel {
  position: fixed;
  ${setting.position}: ${setting.peekSize}px;
  top: ${setting.top};
  padding: 0 20px 0 80px;
  transform: translate(${offsetSignal}100%, -50%);
  transition: all .2s;
  height: ${setting.height}px;
  border-radius: ${setting.height / 2}px;
  opacity: .6;
  background: red;
  z-index: ${setting.zIndex};

  display: flex;
  flex-direction: row;
  align-items: stretch;
}

#sc-panel.active {
  transform: translate(${offsetSignal}${setting.peekSize * 2}px, -50%);
  box-shadow: 0 0 10px rgba(255, 0, 0, .4);
  opacity: 1;
}

#sc-panel-triggle {
  position: absolute;
  left: -${isLeft ? 0 : setting.triggleHor};
  right: -${isLeft ? setting.triggleHor : 0};
  top: -${setting.triggleVer};
  bottom: -${setting.triggleVer};
  z-index: ${setting.zIndex - 1};
}

#sc-panel .sc-panel-item {
  position: relative;
  z-index: ${setting.zIndex + 1};
  color: white;
  font-size: 12px;
  box-sizing: content-box;

  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  transition: background .3s;
}

#sc-panel .sc-panel-item:hover {
  background: rgba(255, 255, 255, .2);
}

#sc-panel .scf {
  font-size: 2em;
  margin-bottom: 2px;
}

#sc-panel .scf-setting {
  position: absolute;
  top: 50%;
  left: 48px;
  transform: translateY(-50%);
  color: rgba(255, 255, 255, .6);
  transition: all .2s;
  cursor: pointer;
  z-index: ${setting.zIndex + 1};
  padding: 4px;
  border-radius: 50%;
}

#sc-panel .scf-setting:hover {
  color: rgba(255, 255, 255, 1);
  background: rgba(255, 255, 255, .2);
}

#sc-panel .sc-setting {
  font-size: 1.4em;
}

#sc-panel-setting {
  z-index: ${setting.zIndex + 2};
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, .6);
  opacity: 0;
  transition: opacity .2s;
}

#sc-panel-setting.active {
  opacity: 1;
}

#sc-panel-setting.none {
  display: none;
}

#sc-setting-box {
  position: absolute;
  top: 120px;
  left: 50%;
  transform: translateX(-50%);

  width: 500px;
  height: 420px;
  background: white;
  border-radius: 4px;
  box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);

  display: flex;
  flex-direction: column;
  align-items: stretch;
}

#sc-setting-box h3 {
  flex: 0 0 auto;

  box-sizing: border-box;
  font-weight: normal;
  font-size: x-large;
  padding: 16px 16px 12px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}

#sc-tab {
  flex-grow: 1;
  overflow: auto;
}

#sc-tab .sc-list {
  padding: 16px;
}

#sc-tab .sc-list li {
  padding: 8px 4px;
  display: flex;
  align-items: baseline;
  transition: background-color .2s;
}

#sc-tab .sc-list li:hover {
  background-color: rgba(0, 0, 0, .1);
}

#sc-tab .sc-list label {
  flex-grow: 1;
  display: inline-block;
  margin: 0 4px;
}

#sc-tab .sc-list .sc-name {
  margin-left: 2px;
}

#sc-setting-box .btn-panel {
  border-top: 1px solid rgba(0, 0, 0, 0.1);
  padding: 10px 16px;
  display: flex;
  justify-content: flex-end;
  align-items: baseline;
}

#sc-setting-box .btn-panel > * {
  margin: 0 4px;
}
`);
}

function appendPanel() {
    var body = document.body;
    if (!body) return;

    var panel = generateEle('div', {
        id: 'sc-panel',
        // className: 'active',
    });

    // panel triggle
    var triggle = generateEle('div', { id: 'sc-panel-triggle' });
    var timerEnter = null;
    var timerLeave = null;
    var funcEnter = function() { addClassName(panel, 'active'); };
    var funcLeave = function() { removeClassName(panel, 'active'); };
    panel.onmouseenter = function() {
        clearTimeout(timerLeave);
        timerEnter = setTimeout(funcEnter, setting.delayEnter);
    }
    panel.onmouseleave = function() {
        clearTimeout(timerEnter);
        timerLeave = setTimeout(funcLeave, setting.delayLeave);
    }
    panel.appendChild(triggle);

    // engines
    enginesArray.forEach(function(entry) {
        var key = entry[0];
        if (key === location.host || !enabledSites.includes(key)) return;
        var engine = entry[1];
        var ele = generateEle('a', {
            className: 'sc-panel-item',
            href: engine.url.replace(/\{q\}/, queryParam()),
        });
        var iconI = generateEle('i', { className: 'scf ' + engine.icon });
        var name = generateEle('span', { innerText: engine.name });
        ele.appendChild(iconI);
        ele.appendChild(name);
        panel.appendChild(ele);
    });

    // setting trigger
    var settingBox = generateEle('div', {
        className: 'scf-setting',
        onclick: showPanelSetting,
    });
    var settingIcon = generateEle('i', { className: 'scf sc-setting' });
    settingBox.appendChild(settingIcon);
    panel.appendChild(settingBox);

    body.appendChild(panel);
}

function appendPanelSetting() {
    var body = document.body;
    if (!body) return;

    var panel = document.getElementById('sc-panel-setting');
    if (panel) return;

    panel = generateEle('div', {
        id: 'sc-panel-setting',
        // className: 'active',
        onclick: hidePanelSetting,
    });

    panel.addEventListener('transitionend', function () {
        if (panel.getAttribute('hide')) {
            addClassName(panel, 'none');
        }
    });

    var box = generateEle('div', {
        id: 'sc-setting-box',
        onclick: function (e) { e.stopPropagation() },
    });

    var title = generateEle('h3', {
        innerText: '搜索引擎配置',
    });
    var settingTab = generateEle('div', { id: 'sc-tab' });

    var listBox = generateEle('ul', {
        className: 'sc-list',
    });
    enginesArray.forEach(function(entry, index) {
        var key = entry[0];
        var item = entry[1];
        var cusId = 'sc-check-' + index;
        var listItem = generateEle('li', {});

        var checkbox = generateEle('input', {
            className: 'sc-site-enabled',
            type: 'checkbox',
            name: key,
            id: cusId,
        });
        var label = generateEle('label', {});
        label.setAttribute('for', cusId);
        var icon = generateEle('i', { className: 'scf ' + item.icon });
        var name = generateEle('span', {
            className: 'sc-name',
            innerText: item.name,
        });
        var suffix = generateEle('a', {
            className: 'sc-href',
            target: '_blank',
            href: 'https://' + key,
            innerText: key,
        });

        label.appendChild(icon);
        label.appendChild(name);
        listItem.appendChild(checkbox);
        listItem.appendChild(label);
        listItem.appendChild(suffix);
        listBox.appendChild(listItem);
    });
    settingTab.appendChild(listBox);

    var buttonPanel = generateEle('div', { className: 'btn-panel' });
    var hint = generateEle('span', {
        innerText: '保存后刷新生效',
    });
    var saveBtn = generateEle('button', {
        innerText: '仅保存',
        onclick: function () {
            storeEnabledSites(panel);
            hidePanelSetting();
        },
    });
    var refreshBtn = generateEle('button', {
        innerText: '保存并刷新',
        onclick: function () {
            storeEnabledSites(panel);
            hidePanelSetting();
            location.reload();
        },
    });
    buttonPanel.appendChild(hint);
    buttonPanel.appendChild(saveBtn);
    buttonPanel.appendChild(refreshBtn);

    box.appendChild(title);
    box.appendChild(settingTab);
    box.appendChild(buttonPanel);
    panel.appendChild(box);
    body.appendChild(panel);
    return panel;
}

function assignEnabledSites(panel) {
    var list = panel.getElementsByClassName('sc-site-enabled');
    for (var i = 0; i < list.length; i++) {
        var input = list[i];
        input.checked = enabledSites.includes(input.name);
    }
}

function storeEnabledSites(panel) {
    var list = panel.getElementsByClassName('sc-site-enabled');
    var results = [];
    for (var i = 0; i < list.length; i++) {
        var input = list[i];
        if (input.checked) {
            results.push(input.name);
        }
    }
    GM_setValue('enabledSites', results);
}

function showPanelSetting() {
    var panel = document.getElementById('sc-panel-setting');
    if (!panel) {
        panel = appendPanelSetting();
    }
    assignEnabledSites(panel);
    panel.setAttribute('hide', '');
    removeClassName(panel, 'none');
    setTimeout(function () {
        addClassName(panel, 'active');
    }, 0);
}

function hidePanelSetting() {
    var panel = document.getElementById('sc-panel-setting');
    if (!panel) {
        panel = appendPanelSetting();
    }
    panel.setAttribute('hide', '1');
    removeClassName(panel, 'active');
}

function generateEle(name, properties) {
    var ele = document.createElement(name);
    Object.entries(properties).forEach(function(it) {
        ele[it[0]] = it[1];
    });
    return ele;
}

function addClassName(ele, name) {
    var classes = (ele.className || '').split(' ').filter(function (it) { return it });
    if (!classes.includes(name)) {
        classes.push(name);
    }
    ele.className = classes.join(' ');
}

function removeClassName(ele, name) {
    var classes = (ele.className || '')
        .split(' ')
        .filter(function (it) { return it && it !== name });
    ele.className = classes.join(' ');
}

function toggleClassName(ele, name) {
    var classes = (ele.className || '')
        .split(' ')
        .filter(function (it) { return it });
    if (classes.includes(name)) {
        classes = classes.filter(function (it) { return it && it !== name });
    } else {
        classes.push(name);
    }
    ele.className = classes.join(' ');
}

function queryParam() {
    var current = engines[location.host];
    if (!current) return '';
    var result = location.href.match(current.match);
    return result && (result[1] || result[0]);
}

function appendExtraCss(url) {
    GM_xmlhttpRequest({
        method: 'GET',
        url: url,
        onload(args) {
            GM_addStyle(args.responseText);
        },
    });
}

(function() {
    'use strict';
    if (!enabledSites.includes(location.host)) return;

    appendStyles();
    appendPanel();
    appendExtraCss('//at.alicdn.com/t/font_1911184_1ib7wqdqydk.css');
})();