Greasy Fork

来自缓存

Greasy Fork is available in English.

Google 検索窓を複製

インスタント検索無効時、検索窓をページ下部にも表示 / When Google Instant is disable, also shows the search box to the page bottom.

当前为 2014-07-21 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Google 検索窓を複製
// @namespace   http://userscripts.org/users/347021
// @id          google-clone-search-box-347021
// @version     2.2.0
// @description インスタント検索無効時、検索窓をページ下部にも表示 / When Google Instant is disable, also shows the search box to the page bottom.
// @include     https://www.google.tld/search*
// @run-at      document-start
// @grant       dummy
// @icon        data:image/vnd.microsoft.icon;base64,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
// @author      100の人
// @homepage    http://greasyfork.icu/ja/scripts/274-google-%E6%A4%9C%E7%B4%A2%E7%AA%93%E3%82%92%E8%A4%87%E8%A3%BD
// @license     Creative Commons Attribution 4.0 International Public License; http://creativecommons.org/licenses/by/4.0/
// ==/UserScript==

(function() {
'use strict';

polyfill();

// body要素挿入時に実行し、Google検索のバージョンを判別する
var textBoxId, inputNodeId, inputParentNodesClassName, textBoxBorderClass, classOnfocuse, previousSiblingId;
startScript(function() {
	var isTargetParent, isTarget, functionsForFirefox, body = document.body;

	if (body.id) {
		if (location.search.contains('tbm=isch')) {
			// 画像検索ページなら実行しない
			return;
		}
		if (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 = function (parent) {
			return parent.id === 'foot';
		};
		isTarget = function (target) {
			return target.id === 'xjs';
		};
		functionsForFirefox = {
			isTargetParent: function (parent) {
				return parent.classList.contains('mw');
			},
			isTarget: function (target) {
				var firstElementChild = target.firstElementChild;
				return firstElementChild && firstElementChild.id === 'foot';
			},
		};
	} else {
		// IE7版 (UAがIE7以下、またはJavaScriptが無効)
		textBoxId = 'tsf';
		previousSiblingId = 'nav';
		isTargetParent = function (parent) {
			return parent.id === 'foot';
		};
		isTarget = function (target) {
			return target.id === 'nav';
		};
		functionsForFirefox = {
			isTargetParent: function (parent) {
				return parent.localName === 'tbody' && parent.parentNode.id === 'mn';
			},
			isTarget: function (target) {
				var cells = target.cells;
				return cells && cells[0] && cells[0].id === 'leftnav';
			},
		};
	}
	startScript(main, isTargetParent, isTarget, function() {
		return document.getElementById(previousSiblingId);
	}, functionsForFirefox);
}, function(parent) {
	return parent.localName === 'html';
}, function(target) {
	return target.localName === 'body';
}, function() {
	return document.body;
});

function main() {
	var style, sheet, cssRules, original, previousSibling,
			bottomForm, textBoxBorder, textBoxBorderClassList, inputParentNodes, submitButton, submitButtonClassList;

	// スタイルシートの設定
	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>');

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

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

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

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

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

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

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

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

	// 検索窓にマウスが載ったとき
	submitButton = bottomForm.getElementsByClassName('gbqfb')[0];
	if (submitButton) {
		submitButtonClassList = submitButton.classList;
		bottomForm.addEventListener('mouseover', function(event) {
			var target = event.target;

			if (textBoxBorder.contains(target)) {
				// 検索窓
				textBoxBorderClassList.add('gbqfqw-hvr', 'gsfe_a');
			} else if (submitButton.contains(target)) {
				// 検索ボタン
				submitButtonClassList.add('gbqfb-hvr');
			}
		});

		bottomForm.addEventListener('mouseout', function(event) {
			var relatedTarget = event.relatedTarget;

			if (!textBoxBorder.contains(relatedTarget)) {
				// 検索窓
				textBoxBorderClassList.remove('gbqfqw-hvr', 'gsfe_a');
			}
			if (!submitButton.contains(relatedTarget)) {
				// 検索ボタン
				submitButtonClassList.remove('gbqfb-hvr');
			}
		});
	}
}



/**
 * 挿入された節の親節が、目印となる節の親節か否かを返すコールバック関数。
 * @callback isTargetParent
 * @param {(Document|Element)} parent
 * @returns {boolean}
 */

/**
 * 挿入された節が、目印となる節か否かを返すコールバック関数。
 * @callback isTarget
 * @param {(DocumentType|Element)} target
 * @returns {boolean}
 */

/**
 * 目印となる節が文書に存在するか否かを返すコールバック関数。
 * @callback existsTarget
 * @returns {boolean}
 */

/**
 * 目印となる節が挿入された直後に関数を実行する。
 * @param {Function} main - 実行する関数。
 * @param {isTargetParent} isTargetParent
 * @param {isTarget} isTarget
 * @param {existsTarget} existsTarget
 * @param {Object} [callbacksForFirefox]
 * @param {isTargetParent} [callbacksForFirefox.isTargetParent] - Firefoxにおける{@link isTargetParent}。
 * @param {isTarget} [callbacksForFirefox.isTarget] - Firefoxにおける{@link isTarget}。
 * @param {boolean} [timeoutSinceStopParsingDocument=0] - DOM構築完了後に監視を続けるミリ秒数。
 * @version 2014-07-21
 */
function startScript(main, isTargetParent, isTarget, existsTarget) {
	/**
	 * {@link checkExistingTarget}で{@link startMain}を実行する間隔(ミリ秒)。
	 * @constant {number}
	 */
	var INTERVAL = 10;
	/**
	 * {@link checkExistingTarget}で{@link startMain}を実行する回数。
	 * @constant {number}
	 */
	var LIMIT = 500;

	/**
	 * 実行済みなら真。
	 * @type {boolean}
	 */
	var alreadyCalled = false;

	// 指定した節が既に存在していれば、即実行
	startMain();
	if (alreadyCalled) {
		return;
	}

	// FirefoxのMutationObserverは、HTMLのDOM構築に関して要素をまとめて挿入したと見なすため、isTargetParent、isTargetを変更
	var callbacksForFirefox = arguments[4];
	if (callbacksForFirefox && typeof sidebar !== 'undefined') {
		if (callbacksForFirefox.isTargetParent) {
			isTargetParent = callbacksForFirefox.isTargetParent;
		}
		if (callbacksForFirefox.isTarget) {
			isTarget = callbacksForFirefox.isTarget;
		}
	}

	var observer = new MutationObserver(mutationCallback);
	observer.observe(document, {
		childList: true,
		subtree: true,
	});

	var timeoutSinceStopParsingDocument = arguments[5] || 0;
	if (document.readyState === 'complete') {
		// DOMの構築が完了していれば
		onDOMContentLoaded();
	} else {
		document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
	}

	/**
	 * {@link startMain}を実行し、スクリプトが開始されていなければさらに{@link timeoutSinceStopParsingDocument}ミリ秒待機し、
	 * スクリプトが開始されていなければ{@link stopObserving}を実行する。
	 */
	function onDOMContentLoaded() {
		startMain();
		if (timeoutSinceStopParsingDocument === 0) {
			if (!alreadyCalled) {
				stopObserving();
			}
		} else {
			window.setTimeout(function () {
				if (!alreadyCalled) {
					stopObserving();
				}
			}, timeoutSinceStopParsingDocument);
		}
		document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
	}

	/**
	 * 目印となる節が挿入されたら、監視を停止し、{@link checkExistingTarget}を実行する。
	 * @param {MutationRecord[]} mutations - A list of MutationRecord objects.
	 * @param {MutationObserver} observer - The constructed MutationObserver object.
	 */
	function mutationCallback(mutations, observer) {
		var slice = Array.prototype.slice, mutation, target, addedNodes, addedNode, i, j, l, l2;
		for (i = 0, l = mutations.length; i < l; i++) {
			mutation = mutations[i];
			target = mutation.target;
			if (target.nodeType === Node.ELEMENT_NODE && isTargetParent(target)) {
				// 子が追加された節が要素節で、かつその節についてisTargetParentが真を返せば
				addedNodes = slice.call(mutation.addedNodes);
				for (j = 0, l2 = addedNodes.length; j < l2; j++) {
					addedNode = addedNodes[j];
					if (addedNode.nodeType === Node.ELEMENT_NODE && isTarget(addedNode)) {
						// 追加された子が要素節で、かつその節についてisTargetが真を返せば
						observer.disconnect();
						checkExistingTarget(0);
						return;
					}
				}
			}
		}
	}

	/**
	 * {@link startMain}を実行し、スクリプトが開始されていなければ再度実行。
	 * @param {number} count - {@link startMain}を実行した回数。
	 */
	function checkExistingTarget(count) {
		startMain();
		if (!alreadyCalled && count < LIMIT) {
			window.setTimeout(checkExistingTarget, INTERVAL, count + 1);
		}
	}

	/**
	 * 指定した節が存在するか確認し、存在すれば{@link stopObserving}を実行しスクリプトを開始。
	 */
	function startMain() {
		if (!alreadyCalled && existsTarget()) {
			stopObserving();
			main();
		}
	}

	/**
	 * 監視を停止する。
	 */
	function stopObserving() {
		alreadyCalled = true;
		if (observer) {
			observer.disconnect();
		}
	}
}

/**
 * ECMAScript仕様のPolyfill。
 */
function polyfill() {
	if (!String.prototype.hasOwnProperty('contains')) {
		/**
		 * Determines whether one string may be found within another string, returning true or false as appropriate.
		 * @param {string} searchString - A string to be searched for within this string.
		 * @param {number} [position=0] - The position in this string at which to begin searching for searchString.
		 * @returns {boolean}
		 * @see {@link http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.contains 21.1.3.6 String.prototype.contains (searchString, position = 0 )}
		 * @see {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/contains String.contains - JavaScript | MDN}
		 * @version polyfill-2013-11-05
		 * @name String.prototype.contains
		 */
		Object.defineProperty(String.prototype, 'contains', {
			writable: true,
			enumerable: false,
			configurable: true,
			value: function (searchString) {
				return this.indexOf(searchString, arguments[1]) !== -1;
			},
		});
	}

	// Polyfill for Firefox 24 ESR
	if (!('EPSILON' in Number)) {
		// Bug 814014 – implement the new classList specification which permits adding/removing several classes with one call <https://bugzilla.mozilla.org/show_bug.cgi?id=814014>
		var DOMTokenListPrototype = DOMTokenList.prototype;
		var handler = {
			apply: function (addOrRemove, domTokenList, argumentList) {
				for (var i = 0, l = argumentList.length; i < l; i++) {
					addOrRemove.call(domTokenList, argumentList[i]);
				}
			},
		};
		DOMTokenListPrototype.add = new Proxy(DOMTokenListPrototype.add, handler);
		DOMTokenListPrototype.remove = new Proxy(DOMTokenListPrototype.remove, handler);
	}
}

})();