Greasy Fork

来自缓存

Greasy Fork is available in English.

Google 検索窓を複製

Also shows the search box to the page bottom.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Google 検索窓を複製
// @name:ja     Google 検索窓を複製
// @description Also shows the search box to the page bottom.
// @description:ja 検索窓をページ下部にも表示します。
// @namespace   http://userscripts.org/users/347021
// @version     3.0.2
// @include     https://www.google.*/*
// @include     https://www.google.*/?*
// @include     https://www.google.*/#*
// @include     https://www.google.*/webhp
// @include     https://www.google.*/webhp?*
// @include     https://www.google.*/webhp#*
// @include     https://www.google.*/search*
// @include     https://www.google.*/search?*
// @include     https://www.google.*/search#*
// @exclude     https://www.google.*/search?*tbm=isch*
// @require     http://greasyfork.icu/scripts/17895/code/polyfill.js?version=189394
// @require     http://greasyfork.icu/scripts/19616/code/utilities.js?version=230651
// @require     http://greasyfork.icu/scripts/17896/code/start-script.js?version=112958
// @license     Mozilla Public License Version 2.0 (MPL 2.0); https://www.mozilla.org/MPL/2.0/
// @compatible  Edge 非推奨 / Deprecated
// @compatible  Firefox
// @compatible  Opera
// @compatible  Chrome
// @grant       dummy
// @run-at      document-start
// @icon        data:image/vnd.microsoft.icon;base64,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
// @author      100の人
// @homepage    http://greasyfork.icu/scripts/274
// ==/UserScript==

(function () {
'use strict';

class GoogleBottomSearchBox
{
	/**
	 * messageイベントで使用する識別子。
	 * @constant {string}
	 */
	static get ID() {return 'google-bottom-search-box-137';}

	constructor()
	{
		startScript(
			() => {
				if (document.querySelector('#csi + script, #csi + a')) {
					// インスタント検索が有効
					this.main();
				} else if (location.pathname === '/search') {
					// インスタント検索が無効
					this.mainWithoutInstant();
				}
			},
			parent => parent.id === 'viewport' || /* インスタンス検索が無効 */ parent === document.body,
			target => target.id === 'main',
			() => document.getElementById('main')
		);
	}

	/**
	 * @param {Event} event
	 */
	handleEvent(event)
	{
		switch (event.type) {
			case 'focus':
				event.target.closest('#sfdiv').classList.add('sbfcn');
				break;
			case 'blur':
				event.target.closest('#sfdiv').classList.remove('sbfcn');
				break;
			case 'mouseup':
				if (event.target.name !== 'q') {
					event.target.closest('form').q.focus();
				}
				break;
			case 'hashchange':
			case 'message':
				if (event.type === 'hashchange'
					|| event.origin === location.origin && typeof event.data === 'object' && event.data !== null
						&& event.data.id === GoogleBottomSearchBox.ID) {
					if (this.getTbm(location) === this.getTbm(new URL(event.oldURL || event.data.oldURL))) {
						document.querySelector('#foot ~ form [name="q"]').value
							= new URLSearchParams(location.hash.replace('#', '')).get('q') || '';
					}
				}
				break;
		}
	}

	/**
	 * @access protected
	 */
	main()
	{
		this.observeFooterInserting(() => {
			if (!document.querySelector('#foot ~ form [name="q"]')) {
				this.cloneForm();
				this.synchronizeSearchWord();
				this.waitSearchControl().then(() => this.setEventListeners());
			}
		});
	}

	/**
	 * URLのtbmパラメータを取得します。
	 * @access protected
	 * @param {(Location|URL)} url
	 * @returns {string}
	 */
	getTbm(url)
	{
		return new URLSearchParams(url.hash.replace('#', '')).get('tbm')
			|| new URLSearchParams(url.search).get('tbm') || '';
	}

	/**
	 * ページにフッタが挿入されるのを監視します。
	 * @access protected
	 * @param {Function} callback - フッタが挿入されるたびに呼び出されるコールバック関数。
	 * @returns {Promise.<void>}
	 */
	observeFooterInserting(callback)
	{
		new MutationObserver(function (mutations, observer) {
			mutations: for (const mutation of mutations) {
				for (const node of mutation.addedNodes) {
					if (node.id === 'cnt' || node.id === 'foot') {
						callback();
						break mutations;
					}
				}
			}
		}).observe(document.getElementById('main'), {childList: true, subtree: true});
	}

	/**
	 * フォームを複製して挿入します。
 	 * @access protected
	 */
	cloneForm()
	{
		document.head.insertAdjacentHTML('beforeend', `<style>
			.hp #foot ~ form {
				/* トップページから完全に切り替わるまではフォームを表示しない */
				display: none;
			}
			#foot ~ form {
				margin-bottom: 1em;
			}
			:not(.mw) > * > #foot ~ form .tsf-p {
				padding-left: 8px;
			}
			#foot ~ form #sfdiv:hover {
				box-shadow: 0 3px 8px 0 rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.08);
			}
		</style>`);

		const bottomForm = document.getElementById('tsf').cloneNode(true);
		bottomForm.querySelector('#logocont').remove();
		document.getElementById('foot').after(bottomForm);
	}

	/**
	 * テキスト入力欄が置換されるのを待機し、実行時点で置換されて居れなければ、複製して置換します。
 	 * @access protected
 	 * @returns {Promise.<void>}
	 */
	waitSearchControl()
	{
		return new Promise(function (resolve) {
			if (document.getElementsByClassName('sbib_a')[0]) {
				resolve();
			} else {
				const ancestors = document.getElementsByClassName('lst-c');
				new MutationObserver((mutations, observer) => {
					observer.disconnect();
					const clone = ancestors[0].cloneNode(true);
					clone.getElementsByTagName('input')[0].removeAttribute('autocomplete');
					ancestors[1].replaceWith(clone);
					resolve();
				}).observe(ancestors[0], {childList: true});
			}
		});
	}

	/**
	 * フォーカス時とクリック時のイベントリスナーを設定します。
 	 * @access protected
	 */
	setEventListeners()
	{
		const form = document.querySelector('#foot ~ form');
		// 検索窓にフォーカスが移った時
		const q = form.q;
		q.addEventListener('focus', this);
		q.addEventListener('blur', this);
		// 検索窓をクリックしたとき
		form.getElementsByClassName('sbib_a')[0].addEventListener('mouseup', this);
	}

	/**
	 * 疑似ページ移動時、複製した検索窓に検索語句を反映します。
 	 * @access protected
	 */
	synchronizeSearchWord()
	{
		if (!this.alreadyObserved) {
			this.alreadyObserved = true;
			GreasemonkeyUtils.executeOnUnsafeContext(function (id) {
				History.prototype.pushState = new Proxy(History.prototype.pushState, {
					apply(target, thisArg, argumentsList)
					{
						const oldURL = location.href;
						const returnValue = Reflect.apply(target, thisArg, argumentsList);
						window.postMessage({id, oldURL}, location.origin);
						return returnValue;
					},
				});
			}, [GoogleBottomSearchBox.ID]);
			window.addEventListener('message', this);
			window.addEventListener('hashchange', this);
		}
	}

	/**
	 * インスタント検索無効時の処理。
 	 * @access protected
	 */
	mainWithoutInstant()
	{
		// body要素挿入時に実行し、Google検索のバージョンを判別する
		let textBoxId, inputNodeId, inputParentNodesClassName, textBoxBorderClass, classOnfocuse, previousSiblingId;

		let isTargetParent, isTarget, functionsForFirefox;
		if (document.body.id) {
			if (document.body.getAttribute('marginheight')) {
				// User-AgentがFirefox
				textBoxId = 'tsf';
				inputNodeId = 'lst-ib';
				inputParentNodesClassName = 'lst-d';
				textBoxBorderClass = 'lst-td';
				classOnfocuse = ['lst-d-f'];
			} else {
				// Google Chrome版 (UAがOpera、Google Chrome、IE8以降)
				textBoxId = 'gbqf';
				inputNodeId = 'gbqfq';
				inputParentNodesClassName = 'gbqfqwc';
				textBoxBorderClass = 'gbqfqw';
				classOnfocuse = ['gbqfqwf', 'gsfe_b'];
			}
			previousSiblingId = 'xjs';

			isTargetParent = parent => parent.id === 'foot';
			isTarget = target => target.id === 'xjs';
			functionsForFirefox = {
				isTargetParent: parent => parent.classList.contains('mw'),
				isTarget(target)
				{
					const firstElementChild = target.firstElementChild;
					return firstElementChild && firstElementChild.id === 'foot';
				},
			};
		} else {
			// IE7版 (UAがIE7以下、またはJavaScriptが無効)
			textBoxId = 'tsf';
			previousSiblingId = 'nav';
			isTargetParent = parent => parent.id === 'foot';
			isTarget = target => target.id === 'nav';
			functionsForFirefox = {
				isTargetParent: parent => parent.localName === 'tbody' && parent.parentNode.id === 'mn',
				isTarget(target)
				{
					const cells = target.cells;
					return cells && cells[0] && cells[0].id === 'leftnav';
				},
			};
		}

		startScript(
			function () {
				// スタイルシートの設定
				document.head.insertAdjacentHTML('beforeend', `<style>
					#foot form {
						margin-top: 13px;
					}

					#foot > form {
						margin-bottom: 1em;
					}

					/*------------------------------------
						Firefox版
					*/
					#foot .nojsv {
						display: none;
					}
					#foot .tsf-p {
						width: 631px;
						padding-left: 8px;
					}
					#nav {
						margin-bottom: initial !important;
					}
				</style>`);

				// 検索ボックスを取得
				const original = document.getElementById(textBoxId);
				if (!original) {
					return;
				}

				// 複製
				const bottomForm = original.cloneNode(true);

				// 移動先を取得
				const previousSibling = document.getElementById(previousSiblingId);

				// 挿入
				previousSibling.parentNode.insertBefore(bottomForm, previousSibling.nextSibling);

				let textBoxBorder, textBoxBorderClassList, inputParentNodes, submitButtonClassList;

				// ページ描画後のスクリプトによる書き換えを待機
				if (inputParentNodesClassName) {
					inputParentNodes = document.getElementsByClassName(inputParentNodesClassName);
					startScript(
						function () {
							// 後から挿入された検索窓を複製
							const table = inputParentNodes[0].firstElementChild.cloneNode(true);
							// オートコンプリートを有効に
							table.getElementsByTagName('input')[0].removeAttribute('autocomplete');
							// 下の検索窓を置き換え
							inputParentNodes[1].replaceChild(table, inputParentNodes[1].firstElementChild);
						},
						parent => parent.id === 'gs_lc0',
						target => target.id === inputNodeId,
						() => document.querySelector('#' + inputNodeId + '[style]')
					);
				}

				// 検索窓にフォーカスが移った時
				if (textBoxBorderClass) {
					textBoxBorder = bottomForm.getElementsByClassName(textBoxBorderClass)[0];
					textBoxBorderClassList = textBoxBorder.classList;
					textBoxBorder.addEventListener('focus', function () {
						textBoxBorderClassList.add(...classOnfocuse);
					}, true);

					textBoxBorder.addEventListener('blur', function () {
						textBoxBorderClassList.remove(...classOnfocuse);
					}, true);

					// 検索窓をクリックしたとき
					textBoxBorder.addEventListener('click', function (event) {
						if (event.target.localName !== 'input') {
							bottomForm.elements.namedItem('q').focus();
						}
					});
				}

				// 検索窓にマウスが載ったとき
				const submitButton = bottomForm.getElementsByClassName('gbqfb')[0];
				if (submitButton) {
					submitButtonClassList = submitButton.classList;
					bottomForm.addEventListener('mouseover', function (event) {
						if (textBoxBorder.contains(event.target)) {
							// 検索窓
							textBoxBorderClassList.add('gbqfqw-hvr', 'gsfe_a');
						} else if (submitButton.contains(event.target)) {
							// 検索ボタン
							submitButtonClassList.add('gbqfb-hvr');
						}
					});

					bottomForm.addEventListener('mouseout', function (event) {
						if (!textBoxBorder.contains(event.relatedTarget)) {
							// 検索窓
							textBoxBorderClassList.remove('gbqfqw-hvr', 'gsfe_a');
						}
						if (!submitButton.contains(event.relatedTarget)) {
							// 検索ボタン
							submitButtonClassList.remove('gbqfb-hvr');
						}
					});
				}
			},
			isTargetParent,
			isTarget,
			() => document.getElementById(previousSiblingId),
			functionsForFirefox
		);
	}
}

new GoogleBottomSearchBox();

})();