pixiv ツリー型コメント

Changes the comment section's default linear mode to threaded mode.

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

// ==UserScript==
// @name        pixiv ツリー型コメント
// @name:ja     pixiv ツリー型コメント
// @name:en     pixiv Threaded Comments
// @namespace   https://gf.qytechs.cn/users/137
// @version     2.0.0
// @description Changes the comment section's default linear mode to threaded mode.
// @description:ja 返信コメントを返信先の下に移動し、コメント欄を見やすく整理する
// @match       http://www.pixiv.net/member_illust.php?*mode=medium*
// @match       http://www.pixiv.net/novel/show.php?*id=*
// @match       http://www.pixiv.net/group/*?*id=*
// @run-at      document-start
// @grant       dummy
// @icon           data:image/vnd.microsoft.icon;base64,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
// @author      100の人
// @homepage    https://gf.qytechs.cn/scripts/5291-pixiv-%E3%83%84%E3%83%AA%E3%83%BC%E5%9E%8B%E3%82%B3%E3%83%A1%E3%83%B3%E3%83%88
// @license     Creative Commons Attribution 4.0 International Public License; http://creativecommons.org/licenses/by/4.0/
// ==/UserScript==

(function () {
'use strict';

/**
 * ページの種類。
 * @type {string}
 */
var pageType;
switch (window.location.pathname) {
	case '/member_illust.php':
		pageType = 'illust';
		break;
	case '/novel/show.php':
		pageType = 'novel';
		break;
	case '/group/':
	case '/group/show.php':
	case '/group/comment.php':
		pageType = 'group';
		break;
	default:
		return;
}

polyfill();

if (pageType !== 'group') {
	startScript(main,
		function (parent) { return parent.id === 'one_comment'; },
		function (target) { return target.classList.contains(pageType === 'illust' ? 'layout-column-2' : 'worksOptionRight'); },
		function () { return pageType === 'illust'
				? document.querySelector('#one_comment .layout-column-2')
				: document.getElementsByClassName('worksOptionRight')[0]; });
} else {
	startScript(main,
		function (parent) { return parent.id === 'wrapper'; },
		function (target) { return target.id === 'template-drawr-paint-ui'; },
		function () { return document.getElementById('template-drawr-paint-ui'); });
}

function main() {
	document.head.insertAdjacentHTML('beforeend', '<style>' + (pageType !== 'group' ? ' \
		/*==================================== \
			イラスト・小説のコメント欄 \
		*/ \
		/*------------------------------------ \
			余白を修正 \
		*/ \
		._comment-items ._comment-item, \
		._comment-items ._comment-sticker-item { \
			border-top: 1px solid #D6DEE5; \
			display: flex; \
			margin: initial; \
			padding: initial; \
		} \
		._comment-items ._comment-sticker-item { \
			display: block; \
			padding-bottom: 6px; \
		} \
		._comment-items ._comment-item > .user-icon-container, \
		._comment-items .comment:first-child::before { \
			/* アイコン */ \
			flex-shrink: 0; \
			position: initial; \
			margin: 0.2em 0.4em; \
		} \
		._comment-items .comment:first-child::before { \
			/* 削除されたユーザーのアイコン */ \
			content: ""; \
			position: absolute; \
			top: 0; \
			left: 0; \
			display: inline-block; \
			width: 40px; \
			height: 40px; \
			background: url("http://source.pixiv.net/common/images/no_profile_s.png") 0% 0% / 100% content-box; \
			opacity: 0.4; \
		} \
		._comment-items .comment:first-child { \
			margin-left: calc(40px + 1em); \
		} \
		._comment-items .comment, \
		._comment-items .has-action-list .comment, \
		._comment-items .host-user .comment { \
			/* アイコン以外の部分 */ \
			flex-grow: 1; \
			padding: 0; \
			margin: 0.2em; \
		} \
		._comment-items .meta { \
			/* 名前・投稿日時 */ \
			margin-bottom: initial; \
		} \
		#one_comment ._comment-sticker-item .sticker-item { \
			/* スタンプ */ \
			margin-top: 6px; \
			margin-left: 6px; \
		} \
		._comment-items .action-list { \
			/* 返信する・削除するボタン */ \
			position: absolute; \
			top: 0; \
			right: 0; \
		} \
		\
		/*------------------------------------ \
			作者コメント強調方法の修正 \
		*/ \
		._comment-items .host-user .comment > * { \
			/* 名前・投稿日時・返信先・スタンプ */ \
			text-align: initial; \
		} \
		._comment-items .host-user .body { \
			/* 本文 */ \
			float: initial; \
		} \
		._comment-items .host-user { \
			/* 背景色 */ \
			background: lavenderblush; \
		} \
		\
		/*------------------------------------ \
			返信コメント \
		*/ \
		._comment-items .tree > :nth-of-type(n+2) { \
			margin-left: 2em; \
		} \
		._comment-items .tree > :first-of-type.host-user { \
			border-bottom: 1px solid #D6DEE5; \
			margin-bottom: -1px; \
		} \
		\
		/*==================================== \
			イラスト・小説の返信ダイアログ \
		*/ \
		/*------------------------------------ \
			余白を修正 \
		*/ \
		._comment-modal .comment { \
			margin: initial; \
		} \
		._comment-modal .comment, \
		._comment-modal .host-user .comment { \
			padding-left: 90px; \
			padding-right: 20px; \
		} \
		/*------------------------------------ \
			作者コメント強調方法の修正 \
		*/ \
		._comment-modal .host-user .user-icon-container { \
			/* アイコン */ \
			left: 20px; \
		} \
		._comment-modal .host-user .comment > * { \
			/* 名前・投稿日時・返信先・スタンプ */ \
			text-align: initial; \
		} \
		._comment-modal .host-user .body { \
			/* 本文 */ \
			float: initial; \
		} \
		._comment-modal ._comment-item.host-user { \
			/* 背景色 */ \
			background: lavenderblush; \
		} \
		\
		/*==================================== \
			スタンプの表示方法の統一 \
		*/ \
		._comment-sticker-item .sticker { \
			/* 6個連続した時の大きさに */ \
			width: 67px; \
			height: 67px; \
		} \
		.sticker-item .user-icon { \
			/* 単体のスタンプを変換したアイテムのアイコン */ \
			width: 100%; \
		} \
		.comment > .body > p > img { \
			/* 単体の絵文字 */ \
			width: 28px; \
			height: initial; \
		} \
		' + (typeof MozSettingsEvent !== 'undefined' ? ' \
		/*==================================== \
			URLの取得に失敗しているアイコン \
		*/ \
		._comment-items ._comment-item > .user-icon-container[data-profile_img="_s."] { \
			background: url("http://source.pixiv.net/common/images/no_profile_s.png") 0% 0% / 100%; \
		}' : '')
	: ' \
		/*==================================== \
			グループのコメント欄 \
		*/ \
		/*------------------------------------ \
			区切り線 \
		*/ \
		#page-group #timeline li.post div.comment div.post, \
		#page-group #timeline li.post div.comment div.post ~ div.post { \
			border-top: dashed 1px #DEE0E8; \
			border-bottom: none; \
		} \
		#page-group #timeline li.post div.comment div.post::before { \
			content: ""; \
			position: absolute; \
			top: 0; \
			left: 0; \
			right: 0; \
			border-top: dashed 1px #FFFFFF; \
		} \
		#page-group #timeline li.post div.comment { \
			border-bottom: dashed 1px #DEE0E8; \
		} \
		/*------------------------------------ \
			最初のコメント \
		*/ \
		#page-group #timeline li.post div.comment > div.post:first-of-type, \
		#page-group #timeline li.post div.comment > .tree:first-of-type > div.post:first-of-type, \
		#page-group #timeline li.post div.comment > div.post:first-child::before, \
		#page-group #timeline li.post div.comment > .tree:first-child > div.post:first-of-type::before { \
			/* 一番上のコメント */ \
			/* 「以前のコメントを見る」ボタンがある時は、白色の破線は消さない */ \
			border-top: none; \
		} \
		/*------------------------------------ \
			返信コメント \
		*/ \
		#page-group #timeline li.post div.comment .tree > :nth-of-type(n+2) { \
			margin-left: 2em; \
		} \
		#page-group #timeline li.post div.comment .tree .postbody { \
			width: initial; \
		} \
	') + '</style>');

	/**
	 * コメントリスト要素。
	 * @type {HTMLDivElement}
	 */
	var commentList;

	/**
	 * コメント欄のすべてのスタンプを格納するリスト。
	 * @type {?HTMLDivElement}
	 */
	var allStickerList = null;

	/**
	 * スタンプの返信ボタン。
	 * @type {?HTMLUListElement}
	 */
	var itemActionsTemplate = document.getElementsByClassName('_item-actions')[0];

	if (pageType !== 'novel') {
		// 小説ページ以外なら
		/**
		 * {@link MutationObserver#observe}の第2引数に指定するオブジェクト。
		 * @type {MutationObserverInit}
		 */
		var observerOptions = {
			childList: true,
		};

		if (pageType === 'illust') {
			// イラストページ
			commentList = document.getElementsByClassName('_comment-items')[0];
		} else {
			// グループページ
			commentList = document.getElementById('timeline');
			observerOptions.subtree = true;
		}

		moveAllReplyComments();

		// コメントの増減を監視する
		new MutationObserver(function (mutations, observer) {
			var firstMutationRecord = mutations[0];
			var firstAddedNode = firstMutationRecord.addedNodes[0];
			if (firstAddedNode) {
				// コメントが増えていれば
				// 監視を一旦停止
				if (pageType === 'illust' && !firstMutationRecord.previousSibling) {
					// イラストページへのコメント投稿時
					firstAddedNode.style.display = '';
				}
				// 監視を一旦停止して返信コメントを移動する
				observer.disconnect();
				moveAllReplyComments();
				observer.observe(commentList, observerOptions);
			}
		}).observe(commentList, observerOptions);
	}

	/**
	 * すべての返信コメントを返信先コメントの下に移動する。
	 */
	function moveAllReplyComments() {
		if (pageType === 'illust') {
			// イラストページ

			// スタンプを一か所にまとめる
			if (allStickerList) {
				// まとめ先のリストが存在すれば
				if (allStickerList.nextElementSibling) {
					// コメント欄の一番下以外の場所にあれば、移動する
					commentList.appendChild(allStickerList);
				}
			}

			/**
			 * スタンプリスト、またはスタンプリストに含まれないスタンプ。
			 * @type {HTMLDivElement}
			 */
			for (var stickerListOrComment of commentList.querySelectorAll('._comment-sticker-item, ._comment-item .sticker-container')) {
				if (!allStickerList) {
					// まとめ先のリストがまだ作成されていなければ、作成
					commentList.insertAdjacentHTML('beforeend', '<div class="_comment-sticker-item sticker-container sticker-6-item"></div>');
					allStickerList = commentList.lastChild;
				}

				if (stickerListOrComment.classList.contains('_comment-sticker-item')) {
					// スタンプリストなら
					if (stickerListOrComment !== allStickerList) {
						// まとめ先のリストでなければ
						stickerListOrComment.remove();
						for (var sticker of stickerListOrComment.childNodes) {
							allStickerList.appendChild(sticker);
						}
					}
				} else {
					// スタンプなら
					/**
					 * コメントアイテム。
					 * @type {HTMLDivElement}
					 */
					var commentItem = stickerListOrComment.parentNode.parentNode;

					/**
					 * 返信先コメント投稿者名 (作者コメントの場合のみ)。
					 * @type {?HTMLSpanElement}
					 */
					var repliedUserName = commentItem.classList.contains('host-user') ? commentItem.getElementsByClassName('reply-to')[0] : null;

					if (repliedUserName) {
						// 作者コメント、かつ返信コメントなら、返信先のコメントIDを要素に保存
						commentItem.setAttribute('data-reply-to-id', repliedUserName.dataset.id);
					}

					// コメントアイテムをスタンプアイテムに変換
					commentItem.classList.remove('_comment-item');
					commentItem.classList.add('sticker-item');
					commentItem.replaceChild(commentItem.getElementsByClassName('sticker')[0], commentItem.getElementsByClassName('comment')[0]);

					// 返信ボタンを追加
					if (itemActionsTemplate) {
						commentItem.appendChild(itemActionsTemplate.cloneNode(true));
					} else {
						commentItem.insertAdjacentHTML('beforeend', '<ul class="_item-actions"> \
							<li class="reply item"> \
								<i class="_icon-12 _icon-reply"></i> \
							</li> \
						</ul>');
						itemActionsTemplate = commentItem.lastChild;
					}

					// スタンプリスト内にスタンプアイテムを移動
					allStickerList.appendChild(commentItem);
				}
			}

			// 返信コメントの移動
			/**
			 * 返信先コメント投稿者名、または返信スタンプ。
			 * @type {HTMLSpanElement}
			 */
			for (var repliedUserNameOrReplyStickerItem of commentList.querySelectorAll('.reply-to, .sticker-item[data-reply-to-id]')) {
				/**
				 * 返信スタンプなら真。
				 * @type {String}
				 */
				var replySticker = repliedUserNameOrReplyStickerItem.classList.contains('sticker-item');

				/**
				 * 返信先コメントのID。
				 * @type {string}
				 */
				var repliedCommentId = repliedUserNameOrReplyStickerItem.dataset[replySticker ? 'replyToId' : 'id'];

				/**
				 * 返信先コメント。
				 * @type {?HTMLDivElement}
				 */
				var repliedComment = commentList.querySelector('._comment-item[data-id="' + repliedCommentId + '"]');

				if (repliedComment) {
					// 返信先コメントが存在すれば
					/**
					 * 返信コメント。
					 * @type {?HTMLDivElement}
					 */
					var replyComment = null;
					if (replySticker) {
						// スタンプなら、スタンプリストに変換する
						replyComment = allStickerList.cloneNode();
						replyComment.classList.add('host-user');
						replyComment.appendChild(repliedUserNameOrReplyStickerItem);
					} else {
						// 通常のコメントなら
						replyComment = repliedUserNameOrReplyStickerItem.parentNode.parentNode;
						// 返信先コメント投稿者名を削除
						repliedUserNameOrReplyStickerItem.remove();
					}
					moveReplyComment(repliedComment, replyComment);
				}
			}
		} else {
			// グループページ
			var repliedUserNames = document.querySelectorAll('.body > p > a');
			for (var i = repliedUserNames.length - 1; i >= 0; i--) {
				/**
				 * 返信先コメント投稿者名。
				 * @type {HTMLSpanElement}
				 */
				var repliedUserName = repliedUserNames[i];

				/**
				 * 返信先コメント。
				 * @type {?HTMLDivElement}
				 */
				var repliedComment = document.getElementById(repliedUserName.hash.replace('#', ''));

				if (repliedComment) {
					// 返信先コメントが存在すれば
					moveReplyComment(repliedComment, repliedUserName.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode);
					// 返信先コメント投稿者名を削除
					repliedUserName.parentNode.remove();
				}
			}
		}
	}

	/**
	 * 返信コメントを返信先コメントの下に移動する。
	 * @param {HTMLDivElement} repliedComment - 返信先コメント。
	 * @param {HTMLDivElement} replyComment - 返信コメント。
	 */
	function moveReplyComment(repliedComment, replyComment) {
		if (!replyComment.previousElementSibling) {
			var parent = replyComment.parentNode;
			if (parent && parent.classList.contains('tree')) {
				// 返信コメントにラッパー要素が存在すれば
				replyComment = parent;
			}
		}

		/**
		 * 返信先コメントと返信コメントを格納するラッパー要素。
		 * @type {HTMLDivElement}
		 */
		var tree = null;
		if (!repliedComment.previousElementSibling) {
			var parent = repliedComment.parentNode;
			if (parent.classList.contains('tree')) {
				// ラッパー要素がすでに存在すれば
				tree = parent;
			}
		}

		if (!tree) {
			// ラッパー要素が存在しなければ
			// ラッパー要素を作成
			tree = document.createElement('div');
			tree.classList.add('tree');
			// 返信先コメントをラッパー要素に置換
			repliedComment.parentNode.replaceChild(tree, repliedComment);
			// 返信先コメントをラッパーに追加
			tree.appendChild(repliedComment);
		}

		// 返信コメント移動
		if (pageType === 'illust') {
			// イラストページ
			tree.insertBefore(replyComment, repliedComment.nextSibling);
		} else {
			// グループページ
			tree.appendChild(replyComment);
		}
	}
}



/**
 * 挿入された節の親節が、目印となる節の親節か否かを返すコールバック関数。
 * @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 {number} [timeoutSinceStopParsingDocument=0] - DOM構築完了後に監視を続けるミリ秒数。
 * @version 2014-11-25
 * @global
 */
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 MozSettingsEvent !== 'undefined') {
		isTargetParent = callbacksForFirefox.isTargetParent || isTargetParent;
		isTarget = callbacksForFirefox.isTarget || 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);
		}
	}

	/**
	 * 目印となる節が挿入されたら、監視を停止し、{@link checkExistingTarget}を実行する。
	 * @param {MutationRecord[]} mutations - A list of MutationRecord objects.
	 * @param {MutationObserver} observer - The constructed MutationObserver object.
	 */
	function mutationCallback(mutations, observer) {
		for (var mutation of mutations) {
			var target = mutation.target;
			if (target.nodeType === Node.ELEMENT_NODE && isTargetParent(target)) {
				// 子が追加された節が要素節で、かつその節についてisTargetParentが真を返せば
				for (var addedNode of mutation.addedNodes) {
					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();
		}
		document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
	}
}



/**
 * WHATWG仕様のPolyfill。
 */
function polyfill() {
	// Polyfill for Opera and Google Chrome
	if (!('@@iterator' in NodeList.prototype) && !(Symbol.iterator in NodeList.prototype)) {
		/** @version polyfill-2014-12-07 */
		Object.defineProperties(NodeList.prototype, /** @lends NodeList# */ {
			/**
			 * @returns {Iterator.<Array.<number, Node>>}
			 * @function
			 */
			entries: {
				writable: true,
				enumerable: false,
				configurable: true,
				value: function* () {
					for (var i = 0, l = this.length; i < l; i++) {
						yield [i, this[i]];
					}
				}
			},
			/**
			 * @returns {Iterator.<number>}
			 * @function
			 */
			keys: {
				writable: true,
				enumerable: false,
				configurable: true,
				value: function* () {
					for (var i = 0, l = this.length; i < l; i++) {
						yield i;
					}
				}
			},
			/**
			 * @returns {Iterator.<Node>}
			 * @function
			 */
			values: {
				writable: true,
				enumerable: false,
				configurable: true,
				value: function* () {
					for (var i = 0, l = this.length; i < l; i++) {
						yield this[i];
					}
				}
			},
		});
		/**
		 * @returns {Iterator.<Node>}
		 * @function NodeList#@@iterator
		 */
		Object.defineProperty(NodeList.prototype, Symbol.iterator, {
			writable: true,
			enumerable: false,
			configurable: true,
			value: function* () {
				for (var i = 0, l = this.length; i < l; i++) {
					yield this[i];
				}
			}
		});
	}
}


})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址