Greasy Fork is available in English.

Search Cross

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

// ==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');
})();