pixiv 検索オプションを追加

This is a search option add-on script for pixiv. Enables you to select search mode by radio buttons (legacy feature).

// ==UserScript==
// @name        pixiv 検索オプションを追加
// @name:ja     pixiv 検索オプションを追加
// @name:en     pixiv Search Options
// @description This is a search option add-on script for pixiv. Enables you to select search mode by radio buttons (legacy feature).
// @description:ja 検索オプションを、以前のようにラジオボタンで選択出来るようにする。
// @namespace   http://loda.jp/script/
// @version     6.0.1
// @match       https://www.pixiv.net/*
// @exclude     https://www.pixiv.net/member_illust.php?*mode=manga*
// @exclude     https://www.pixiv.net/apps.php*
// @require     https://gitcdn.xyz/cdn/greasemonkey/gm4-polyfill/a834d46afcc7d6f6297829876423f58bb14a0d97/gm4-polyfill.js
// @resource    dialog-polyfill.css https://bowercdn.net/c/dialog-polyfill-0.4.10/dialog-polyfill.css
// @require     https://bowercdn.net/c/dialog-polyfill-0.4.10/dialog-polyfill.js
// @require     https://greasyfork.org/scripts/17895/code/polyfill.js?version=625392
// @require     https://greasyfork.org/scripts/19616/code/utilities.js?version=752462
// @require     https://greasyfork.org/scripts/17896/code/start-script.js?version=112958
// @license     MPL-2.0
// @contributionURL https://www.amazon.co.jp/registry/wishlist/E7PJ5C3K7AM2
// @compatible  Edge 非推奨 / Deprecated
// @compatible  Firefox
// @compatible  Opera
// @compatible  Chrome
// @grant       GM.getValue
// @grant       GM_getValue
// @grant       GM.setValue
// @grant       GM_setValue
// @grant       GM.registerMenuCommand
// @grant       GM_registerMenuCommand
// @grant       GM.getResourceUrl
// @grant       GM_getResourceURL
// @noframes
// @run-at      document-start
// @icon        data:image/png;base64,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
// @author      100の人
// @homepageURL https://greasyfork.org/scripts/265
// ==/UserScript==

// 当スクリプトはpixivが作成、配布しているアプリケーションではありません。
// <https://www.pixiv.net/terms/?page=brand>

'use strict';

// L10N
Gettext.setLocalizedTexts({
	/*eslint-disable quote-props, max-len */
	'en': {
		'完全一致': 'Exact match',
		'部分一致': 'Partial match',
		'タイトル・キャプション': 'Title/Description',
		'小説': 'Novels',
		'タグ': 'Tags',
		'キーワード': 'Keyword',
		'本文': 'Content',
		'ユーザー': 'User',
		'グループ': 'Groups',
		'すべて': 'All',
		'検索': 'Search',
		'小説検索': 'Search novel',
		'ユーザー検索': 'Search user',
		'グループ検索': 'Search group',
		'pixiv 検索オプションを追加': 'pixiv Search Options',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': 'If you set this, the page header becomes higher.',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': 'If you have opened pixiv pages in other tabs, this setting will reflect after the tab refresh.',
		'イラスト・マンガの検索結果ページで、検索モードを維持': 'In search result pages of illusts and mangas, maintains current search mode',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': 'If you unset this, selects “Partial match” radio button by default.',
	},
	'fr': {
		'完全一致': 'Concordance parfaite',
		'部分一致': 'Concordance partielle',
		'タイトル・キャプション': 'Titre, Légende',
		'小説': 'Roman',
		'タグ': 'Mots-clés',
		'キーワード': 'Mots-clés',
		'本文': 'Contenu',
		'ユーザー': 'Utilisateur',
		'グループ': 'Groups',
		'すべて': 'Tout',
		'検索': 'Rechercher',
		'小説検索': 'Rechercher un roman',
		'ユーザー検索': '',
		'グループ検索': 'Rechercher un groupe',
		'pixiv 検索オプションを追加': '',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': '',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': '',
		'イラスト・マンガの検索結果ページで、検索モードを維持': '',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': '',
	},
	'ko': {
		'完全一致': '완전 일치',
		'部分一致': '부분 일치',
		'タイトル・キャプション': '제목・캡션',
		'小説': '소설',
		'タグ': '태그',
		'キーワード': '키워드',
		'本文': '본문',
		'ユーザー': '유저',
		'グループ': '그룹',
		'すべて': '전체',
		'検索': '검색',
		'小説検索': '소설 검색',
		'ユーザー検索': '유저 검색',
		'グループ検索': '그룹 검색',
		'pixiv 検索オプションを追加': '',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': '',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': '',
		'イラスト・マンガの検索結果ページで、検索モードを維持': '',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': '',
	},
	'ru': {
		'完全一致': 'Полное совпадение',
		'部分一致': 'Частичное совпадение',
		'タイトル・キャプション': 'Заголовок',
		'小説': 'Рассказы',
		'タグ': 'Метка',
		'キーワード': 'Ключевые слова',
		'本文': 'Текст',
		'ユーザー': 'Пользователь',
		'グループ': 'Группа',
		'すべて': 'Все',
		'検索': 'Поиск',
		'小説検索': 'Искать рассказ',
		'ユーザー検索': 'Искать пользователя',
		'グループ検索': 'Искать группу',
		'pixiv 検索オプションを追加': '',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': '',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': '',
		'イラスト・マンガの検索結果ページで、検索モードを維持': '',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': '',
	},
	'th': {
		'完全一致': '',
		'部分一致': '',
		'タイトル・キャプション': 'ชื่อและคำบรรยาย',
		'小説': 'นิยาย',
		'タグ': 'แท็ก',
		'キーワード': 'คีย์เวิร์ด',
		'本文': '',
		'ユーザー': 'ผู้ใช้',
		'グループ': '',
		'すべて': 'ทั้งหมด',
		'検索': 'ค้นหา',
		'小説検索': '',
		'ユーザー検索': '',
		'グループ検索': '',
		'pixiv 検索オプションを追加': '',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': '',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': '',
		'イラスト・マンガの検索結果ページで、検索モードを維持': '',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': '',
	},
	'zh': {
		'完全一致': '完全相同',
		'部分一致': '部分相同',
		'タイトル・キャプション': '题目/简述',
		'小説': '小说',
		'タグ': '标签',
		'キーワード': '关键词',
		'本文': '内容',
		'ユーザー': '用户',
		'グループ': '群组',
		'すべて': '全部',
		'検索': '搜索',
		'小説検索': '搜索小说',
		'ユーザー検索': '搜索用户',
		'グループ検索': '搜索群组',
		'pixiv 検索オプションを追加': '',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': '',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': '',
		'イラスト・マンガの検索結果ページで、検索モードを維持': '',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': '',
	},
	'zh-tw': {
		'完全一致': '完全相同',
		'部分一致': '部分相同',
		'タイトル・キャプション': '題目/簡述',
		'小説': '小說',
		'タグ': '標籤',
		'キーワード': '關鍵詞',
		'本文': '內容',
		'ユーザー': '用戶',
		'グループ': '群組',
		'すべて': '全部',
		'検索': '搜索',
		'小説検索': '搜索小說',
		'ユーザー検索': '搜索用戶',
		'グループ検索': '搜索群組',
		'pixiv 検索オプションを追加': '',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': '',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': '',
		'イラスト・マンガの検索結果ページで、検索モードを維持': '',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': '',
	},
	'es': {
		'完全一致': 'Coincidencia exacta',
		'部分一致': 'Coincidencia parcial',
		'タイトル・キャプション': 'Título/Descripción',
		'小説': 'Novelas',
		'タグ': 'Etiquetas',
		'キーワード': 'Palabra clave',
		'本文': 'Mensaje',
		'ユーザー': 'Usuarios',
		'グループ': 'Grupo',
		'すべて': 'Todos',
		'検索': 'Buscar',
		'小説検索': 'Buscar novela',
		'ユーザー検索': 'Buscar usuario',
		'グループ検索': 'Buscar grupo',
		'pixiv 検索オプションを追加': '',
		'この設定をオンにすると、ヘッダの高さが大きくなります。': '',
		'別のタブでpixivを開いていた場合、この設定の変更はそのタブの再読み込み後に反映されます。': '',
		'イラスト・マンガの検索結果ページで、検索モードを維持': '',
		'この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。': '',
	},
	/*eslint-enable quote-props, max-len */
});

/**
 * スクリプトの起動。
 */
class PixivSearchOptions
{
	constructor()
	{
		this.id = Math.random();

		this.observeURLChanged();

		startScript(
			() => {
				if (document.body.classList.contains('not-logged-in')) {
					return;
				}

				/**
				 * 検索窓。
				 * @type {HTMLInputElement}
				 * @access protected
				 */
				this.word = document.querySelector(
					'#root form input[type="text"], #js-mount-point-header form input[type="text"]' // 後者は仕様変更前
				);

				this.main();
			},
			parent => parent.matches('#root, #js-mount-point-header div'), // 後者は仕様変更前
			target => target.matches('#root > div:first-child')
				|| /* 仕様変更前 */ target.firstElementChild && target.firstElementChild.localName === 'form',
			() => document.querySelector(
				'#root form input[type="text"], #js-mount-point-header form input[type="text"]' // 後者は仕様変更前
			),
			{},
			60 * 1000
		);
	}

	observeURLChanged()
	{
		addEventListener('message', this);

		GreasemonkeyUtils.executeOnUnsafeContext(function (id) {
			History.prototype.pushState = new Proxy(History.prototype.pushState, {
				apply(target, thisArg, argumentsList)
				{
					postMessage({ id, url: argumentsList[2] }, location.origin);
					return Reflect.apply(target, thisArg, argumentsList);
				},
			});
		}, [this.id]);
	}

	/**
	 * @param {Event}
	 */
	handleEvent(event)
	{
		switch (event.type) {
			case 'message': {
				if (event.origin !== location.origin || typeof event.data !== 'object' || event.data === null
					|| event.data.id !== this.id || !event.data.url) {
					break;
				}
				this.reflectSearchMode(new URL(event.data.url, location));
				break;
			}
			case 'submit': {
				const form = event.target;
				if (this.word.value === '') {
					break;
				}

				const searchMode = form.querySelector('[name="s_mode"]:checked');
				const subOption = form.querySelector('[name="s_sub_mode"]:checked');

				if (searchMode.value === 's_tag' || searchMode.value === 's_novel' && subOption.value === 's_tag') {
					// 検索窓のデフォルトの検索モードなら
					break;
				}

				event.stopPropagation();

				if (searchMode.dataset.formAction) {
					form.action = searchMode.dataset.formAction;
				}
				this.word.name = searchMode.dataset.wordName || 'word';
				if (searchMode.dataset.modeName) {
					searchMode.name = searchMode.dataset.modeName;
				}

				const novel = searchMode.value === 's_novel';
				if (subOption) {
					// 副検索モードが存在すれば
					searchMode.value = subOption.value;
					subOption.removeAttribute('name');
				}

				if (['s_usr', 's_group'].includes(searchMode.value)) {
					// ユーザー、グループの検索なら
					if (form.action !== location.origin + location.pathname) {
						// サービス間をまたぐ場合、不要なパラメーターは送信しない
						for (const hiddenParam of form.querySelectorAll('[type="hidden"]')) {
							hiddenParam.disabled = true;
						}
					}
					break;
				}

				const result = /^\/tags\/[^/]+\/([a-z]+)$/.exec(location.pathname);
				const currentMode = result ? result[1] : '';
				if (!currentMode || currentMode !== 'novels' && novel || currentMode === 'novels' && !novel
					|| decodeURIComponent(/^\/tags\/([^/]+)/u.exec(location.pathname)[1]) !== this.word.value) {
					// 検索結果以外のページ、サービス間をまたぐ場合、
					// またはキーワードが現在のページと異なる場合
					form.action = `/tags/${encodeURIComponent(this.word.value)}/${novel ? 'novels' : 'artworks'}`;
					this.word.removeAttribute('name');
					if (searchMode.value === 's_tag_full') {
						// タグ完全一致なら、s_modeを削除
						searchMode.removeAttribute('name');
					}
					break;
				}

				// イラストからイラスト、小説から小説の場合
				event.preventDefault();

				// 検索オプションボタンをクリック
				document.querySelector('[d^="M0 1C0 0.447754"]').closest('button').click();
				new MutationObserver((mutations, observer) => {
					let dialog;
					for (const mutation of mutations) {
						dialog = Array.from(mutation.addedNodes).find(
							node => node.nodeType === Node.ELEMENT_NODE && node.getAttribute('role') === 'presentation'
						);
						if (dialog) {
							break;
						}
					}
					if (!dialog) {
						return;
					}
					observer.disconnect();

					dialog.hidden = true;

					// 対象
					const input = dialog.querySelectorAll('[class$="-dummyInput"]')[novel ? 0 : 1];
					// プルダウンメニューを開く
					input.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: ' ' }));
					new MutationObserver(function (mutations, observer) {
						let select, options;
						mutations: for (const mutation of mutations) {
							for (const node of mutation.addedNodes) {
								options = node.querySelectorAll('[role="option"]');
								if (options.length === 0) {
									continue;
								}
								select = node;
								break mutations;
							}
						}
						if (!select) {
							return;
						}
						observer.disconnect();

						// 項目を選択する
						options[(novel
							? ['s_tag_only', 's_tag_full', 's_tc', 's_tag']
							: ['s_tag', 's_tag_full', 's_tc']).indexOf(searchMode.value)].click();
						new MutationObserver(function (mutations, observer) {
							if (!mutations.some(mutation => Array.from(mutation.removedNodes).includes(select))) {
								return;
							}
							observer.disconnect();
							
							setTimeout(function () {
								// 「適用する」ボタンを押す
								dialog.querySelector('[type="submit"]').click();
							}, 100);
						}).observe(select.parentElement, { childList: true });
					}).observe(dialog, { subtree: true, childList: true });
				}).observe(document.body, { childList: true });
				break;
			}
			case 'change': {
				// 副検索モードの選択
				const subOptions = event.target.parentElement.parentElement;
				if (event.target.name === 's_mode') {
					// 検索モードの選択なら
					if (subOptions.classList.contains('sub-options')) {
						// 副検索モードが存在すれば
						subOptions.querySelector('[value="s_tag"], [value="keyword_all"]').checked = true;
					} else {
						const subOption = event.currentTarget.querySelector('[name="s_sub_mode"]:checked');
						if (subOption) {
							subOption.checked = false;
						}
					}
					this.word.placeholder = event.target.dataset.placeholder;
				} else if (event.target.name === 's_sub_mode') {
					// 副検索モードの選択なら
					const subOption = subOptions.firstElementChild.firstElementChild;
					subOption.checked = true;
					this.word.placeholder = subOption.dataset.placeholder;
				}
				break;
			}
			case 'dblclick':
				// ラベルをダブルクリックで検索
				if (event.target.matches('label, label *')) {
					event.currentTarget.dispatchEvent(new Event('submit'));
				}
				break;
		}
	}

	main()
	{
		// 言語の設定
		Gettext.setLocale(document.documentElement.lang);

		/**
		 * 検索オプションを設定するラジオボタンのHTML文字列。
		 * @type {string}
		 */
		const optionsHTML = h`
			<label>
				<input data-placeholder="${_('検索')}" value="s_tag_full" name="s_mode" type="radio" />
				${_('完全一致')}
			</label>
			<label>
				<input data-placeholder="${_('検索')}" value="s_tag" name="s_mode" type="radio" />
				${_('部分一致')}
			</label>
			<label>
				<input data-placeholder="${_('検索')}" value="s_tc" name="s_mode" type="radio" />
				${_('タイトル・キャプション')}
			</label>
			<div id="s_novel" class="sub-options" title="${_('小説')}">
				<label>
					<input data-placeholder="${_('小説検索')}" value="s_novel" name="s_mode" type="radio" />
					<img alt="${_('小説')}" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAARBAMAAADNtor0AAAAD1BMVEVldC2Spb6susrCzdr9//waY6iPAAAAAXRSTlMAQObYZgAAAEFJREFUCNdjYBAUZIABERdDBNNZAM6ECQsCmY6CQCDAIAJiugABJtPZ2NiEfFFDIROYqIuzMkzUxcXZCMIUhAEBACaMGgfSfm2+AAAAAElFTkSuQmCC" />
				</label>
				<label>
					<input value="s_tag_full" name="s_sub_mode" type="radio" />
					${_('タグ')}
				</label>
				<label>
					<input value="s_tag" name="s_sub_mode" type="radio" />
					${_('キーワード')}
				</label>
				<label>
					<input value="s_tc" name="s_sub_mode" type="radio" />
					${_('本文')}
				</label>
			</div>
			<label title="${_('ユーザー')}">
				<input data-placeholder="${_('ユーザー検索')}" data-word-name="nick"
					data-form-action="https://www.pixiv.net/search_user.php" value="s_usr" name="s_mode" type="radio" />
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="16px" height="16px">
					<title>${_('ユーザー')}</title>
					<path fill="#809DB8" d="M848,348 l98,-20 q28,0 54,22 q38,32 48,44 q22,26 34,26 q16,0 26,-4 q14,-6 52,2 q32,6 48,24 q18,20 23,42 t5,50 q0,32 2,48 q-4,24 -2,38 q6,36 6,62 t-6,44 t12,28 t38,10 h22 q28,0 40,46 q8,30 8,60 q0,14 -2,30 l-6,46 q-2,16 -6,22 q-6,10 -10,16 t-14,14 l-18,12 l-24,16 q-18,10 -30,18 q-22,62 -50,102 q-34,48 -58,70 l-22,22 q-10,96 -10,118 v6 q0,8 2,16 q0,8 4,22 q4,18 12,28 l20,28 q10,14 30,28 q8,6 46,26 q26,14 62,24 q46,12 82,18 q88,12 138,28 q60,18 102,44 t62,40 t50,46 q28,30 30,32 q20,22 40,98 q20,74 20,140 v66 h-1768 v-72 q0,-42 14,-126 q16,-92 34,-106 q8,-6 32,-34 t46,-48 t62,-46 q36,-24 100,-44 q66,-22 140,-32 q60,-8 102,-22 q40,-12 74,-32 t50,-34 q26,-24 34,-36 q10,-16 16,-30 q12,-18 10,-20 q-4,-6 -2,-6 v-132 q-4,-2 -12,-8 q-8,-4 -28,-22 q-14,-10 -40,-36 q-18,-18 -38,-50 t-32,-64 l-36,-18 q-20,-10 -24,-14 q-4,-2 -12,-18 q-8,-14 -8,-16 q0,-4 -2,-18 t-4,-22 q0,-4 2,-10 t5,-26 t3,-38 q0,-20 8,-38 q10,-32 12,-36 q8,-14 18,-24 t22,-10 h14 q8,-2 18,-12 q12,-12 8,-30 q2,-64 2,-134 q0,-22 8,-50 q10,-38 18,-58 q12,-32 38,-56 t60,-32 l52,-12 h-2 q2,0 6,-2 q10,-2 12,-2 v-6 v-4 q-2,-4 0,-6 q0,-2 8,-4 t22,-2 z" />
				</svg>
			</label>
			<div id="s_group" class="sub-options" title="${_('グループ')}">
				<label>
					<input data-placeholder="${_('グループ検索')}" data-mode-name="mode"
						data-form-action="https://www.pixiv.net/group/search_group.php"
						value="s_group" name="s_mode" type="radio" />
					<img alt="${_('グループ')}" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEWLpryswdDV3ubr8POkfP3KAAAAT0lEQVQI12NYBQQMYGLd7/e/GFYtX/6LYfX27fsYVqU/i2JYwX4hjGEJk4MYw0Y2hSiGLUwNcgzrVr9aBxLjY1jCDOJGP93FsPrf/ldgUwDsuCQfQnDv1AAAAABJRU5ErkJggg==" />
				</label>
				<label>
					<input value="keyword_all" name="s_sub_mode" type="radio" />
					${_('すべて')}
				</label>
				<label>
					<input value="tag_full" name="s_sub_mode" type="radio" />
					${_('完全一致')}
				</label>
				<label>
					<input value="tag" name="s_sub_mode" type="radio" />
					${_('部分一致')}
				</label>
				<label>
					<input value="keyword" name="s_sub_mode" type="radio" />
					${_('タイトル・キャプション')}
				</label>
			</div>`;

		const form = this.word.form;
		form.id = 'suggest-container';
		
		this.style({
			gridElementSelector: Array.from(form.parentElement.parentElement.classList).map(cls => '.' + cls).join(''),
		});

		// 検索オプションを設定するラジオボタンの追加
		form.insertAdjacentHTML('beforeend', optionsHTML);

		form.parentElement.addEventListener('submit', this, true);
		form.addEventListener('change', this);
		form.addEventListener('dblclick', this);

		// 現在の検索モードを設定
		this.reflectSearchMode(location);
	}

	/**
	 * 現在のページの検索モードをもとに、検索窓の検索モードを設定します。
	 * @param {string} url
	 * @returns {void}
	 */
	reflectSearchMode(url)
	{
		const { pathname, searchParams } = new URL(url);

		let mode, subMode;

		const form = this.word.form;

		if (pathname.startsWith('/tags/')) {
			mode = searchParams.get('s_mode');
			if (pathname.endsWith('novels')) {
				// 小説の検索結果
				subMode = mode;
				mode = 's_novel';
			} else {
				// イラストの検索結果
				const currentMode = mode || 's_tag_full';
				mode = 's_tag';

				GM.getValue('check-match-full-by-default-on-full-match-result', false).then(function (value) {
					if (value) {
						form.querySelector(`[value="${currentMode}"]`).click();
					}
				});
			}
		} else if (pathname.startsWith('/novel/')) {
			mode = 's_novel';
		} else if (pathname === '/search_user.php') {
			mode = 's_usr';
		} else if (pathname.startsWith('/group/')) {
			mode = 's_group';
		} else {
			mode = 's_tag';
		}
		
		form.querySelector(!subMode ? `[value="${mode}"]` : `#${mode} [value="${subMode}"]`).click();
	}

	/**
	 * スタイルシートを設定します。
	 * @access protected
	 */
	style({ gridElementSelector })
	{
		document.head.insertAdjacentHTML('beforeend', `<style>
			${gridElementSelector} {
				grid-template-columns: 1fr minmax(auto, 970px) 1fr;
			}

			#suggest-container {
				display: flex;
				align-items: center;
				justify-content: center;
			}

			/*------------------------------------
				検索オプション
			*/
			#suggest-container > div:first-of-type {
				padding-right: 1em;
			}
			#suggest-container label {
				padding: 0 0.7em;
				display: flex;
				align-items: center;
				white-space: nowrap;
			}
			#suggest-container label input {
				margin-right: 0.3em;
				width: initial;
			}

			/*------------------------------------
				副検索モード
			*/
			#suggest-container .sub-options label:not(:first-of-type) {
				display: none;
				position: absolute;
				z-index: 1;
				width: 13em;
				height: 2em;
				border: solid 1px #D6DEE5;
				border-top-style: none;
				border-bottom-style: none;
				background: #FFFFFF;
			}
			#suggest-container .sub-options label:nth-of-type(2) {
				border-top-style: solid;
				border-radius: 5px 5px 0 0;
			}
			#suggest-container .sub-options label:nth-of-type(3) {
				margin-top: 2em;
			}
			#suggest-container .sub-options label:nth-of-type(4) {
				margin-top: 4em;
			}
			#suggest-container .sub-options label:nth-of-type(5) {
				margin-top: 6em;
			}
			#suggest-container .sub-options label:last-of-type {
				border-bottom-style: solid;
				border-radius: 0 0 5px 5px;
			}
			#suggest-container .sub-options:hover label {
				display: flex;
			}
			#suggest-container .sub-options:hover::after {
				display: block;
			}
		</style>`);
	}
}

const pixivSearchOptions = new PixivSearchOptions();

/**
 * 設定を管理します。
 */
class PixivSearchOptionsSettings
{
	constructor()
	{
		startScript(
			async () => {
				GM.registerMenuCommand(_('pixiv 検索オプションを追加'), () => this.showDialog());
			},
			parent => parent === document.documentElement,
			target => target === document.body,
			() => document.body
		);
	}

	/**
	 * 設定ダイアログを表示します。
	 */
	async showDialog()
	{
		if (!this.dialog) {
			this.initialize();
		}

		const form = document.forms['pixiv-search-options-settings'];
		form['check-match-full-by-default-on-full-match-result'].checked
			= await GM.getValue('check-match-full-by-default-on-full-match-result', false);

		this.dialog.showModal();
	}

	/**
	 * @param {Event} event
	 */
	handleEvent(event)
	{
		switch (event.type) {
			case 'change':
				GM.setValue(event.target.name, event.target.checked);
				break;

			case 'click':
				this.dialog.close();
				break;
		}
	}

	/**
	 * 設定ダイアログを構築します。
	 * @access private
	 */
	initialize()
	{
		document.head.insertAdjacentHTML('beforeend', `<style>
			/*====================================
				pixiv 検索オプションを表示 設定ダイアログ
			*/
			#pixiv-search-options-settings {
				border-radius: 0.5em;
				border-color: #69AFCA;
				border-width: 0.5em;
			}

			#pixiv-search-options-settings h1 {
				font-size: 1.5em;
				font-weight: bold;
				margin-bottom: 0.5em;
				text-align: center;
			}

			#pixiv-search-options-settings form {
				color: #333333;
			}

			#pixiv-search-options-settings form ul li {
				margin: 0.2em 0;
			}

			#pixiv-search-options-settings form label {
				display: flex;
				align-items: center;
			}

			#pixiv-search-options-settings form small {
				margin-left: 1.3em;
			}

			#pixiv-search-options-settings form [name="close"] {
				border: none;
				position: absolute;
				border-radius: 50%;
				position: absolute;
				top: -1em;
				right: -1em;
				width: 2.5em;
				height: 2.5em;
				background:
					black url("https://source.pixiv.net/www/images/common/icon_modal_close.png") no-repeat 50% 50%;
				cursor: pointer;
				overflow: hidden;
				white-space: nowrap;
				text-indent: 200%;
			}
		</style>`);

		document.body.insertAdjacentHTML('afterbegin', h`<dialog id="pixiv-search-options-settings">
			<h1>${_('pixiv 検索オプションを追加')}</h1>
			<form name="pixiv-search-options-settings">
				<ul>
					<li>
						<label>
							<input type="checkbox" name="check-match-full-by-default-on-full-match-result" />
								${_('イラスト・マンガの検索結果ページで、検索モードを維持')}
						</label>
						<p><small class="settingColor">${_('この設定をオフにすると、最初は「部分一致」にチェックが入った状態になります。')}</small></p>
					</li>
				</ul>
				<button type="button" name="close">${_('閉じる')}</button>
			</form>
		</dialog>`);

		this.dialog = document.getElementById('pixiv-search-options-settings');
		this.polyfillDialogElements([this.dialog]);

		const form = document.forms['pixiv-search-options-settings'];
		form.addEventListener('change', this);
		form.close.addEventListener('click', this);
	}

	/**
	 * Microsoft Edge、およびFirefox向けに、dialog要素のpolyfillを行います。
	 * @access private
	 * @param {HTMLDialogElement[]} dialogs
	 * @returns {Promise.<void>}
	 */
	async polyfillDialogElements(dialogs)
	{
		document.head.insertAdjacentHTML(
			'beforeend',
			h`<link rel="stylesheet" href="${await GM.getResourceUrl('dialog-polyfill.css')}" />`
		);

		for (const dialog of dialogs) {
			/*globals dialogPolyfill */
			dialogPolyfill.registerDialog(dialog);
		}
	}
}

new PixivSearchOptionsSettings();