Github Reply Comments

Easy reply to Github comments

当前为 2020-07-18 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Github Reply Comments
// @namespace   https://github.com/jerone/UserScripts
// @description Easy reply to Github comments
// @author      jerone
// @copyright   2016+, jerone (https://github.com/jerone)
// @license     CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
// @license     GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @homepage    https://github.com/jerone/UserScripts/tree/master/Github_Reply_Comments
// @homepageURL https://github.com/jerone/UserScripts/tree/master/Github_Reply_Comments
// @supportURL  https://github.com/jerone/UserScripts/issues
// @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCYMHWQ7ZMBKW
// @version     1.0.5
// @icon        https://github.githubassets.com/pinned-octocat.svg
// @grant       none
// @include     https://github.com/*
// @include     https://gist.github.com/*
// @require     https://unpkg.com/[email protected]/dist/turndown.js
// @require     https://unpkg.com/[email protected]/dist/turndown-plugin-gfm.js
// @require     https://unpkg.com/[email protected]/turndown-plugin-github-code-snippet.js
// ==/UserScript==

(function () {

	String.format = function (string) {
		var args = Array.prototype.slice.call(arguments, 1, arguments.length);
		return string.replace(/{(\d+)}/g, function (match, number) {
			return typeof args[number] !== "undefined" ? args[number] : match;
		});
	};

	var turndownService = new TurndownService({
		headingStyle: 'atx',
		codeBlockStyle: 'fenced',
		hr: '***'
	});
	turndownService.use(turndownPluginGfm.gfm);
	turndownService.use(turndownPluginGithubCodeSnippet);

	function getCommentTextarea(replyBtn) {
		var newComment = replyBtn;
		while (newComment && !newComment.classList.contains("js-quote-selection-container")) {
			newComment = newComment.parentNode;
		}

		var inlineComment = newComment.querySelector(".js-inline-comment-form-container");
		if (inlineComment) {
			inlineComment.classList.add("open");
		}

		var textareas = newComment.querySelectorAll(":scope > :not(.last-review-thread) .comment-form-textarea:not(.github-writer-ckeditor)");
		return textareas[textareas.length - 1];
	}

	function getCommentMarkdown(comment) {
		var commentText = "";

		// Use raw comment when available.
		var commentForm = comment.querySelector(".comment-form-textarea");
		if (commentForm) {
			commentText = commentForm.value;
		}

		// Convert comment HTML to markdown.
		if (!commentText) {
			// Clone it, so we can alter the HTML a bit, without modifing the page.
			var commentBody = comment.querySelector(".comment-body").cloneNode(true);

			// Remove 'Toggle code wrap' buttons from https://greasyfork.org/en/scripts/18789-github-toggle-code-wrap
			Array.prototype.forEach.call(commentBody.querySelectorAll(".ghd-wrap-toggle"), function (ghd) {
				ghd.remove();
			});

			// GitHub add an extra new line, which is converted by Turndown.
			Array.prototype.forEach.call(commentBody.querySelectorAll("pre code"), function (pre) {
				pre.innerHTML = pre.innerHTML.replace(/\n$/g, '');
			});

			commentText = turndownService.turndown(commentBody.innerHTML);
		}

		return commentText;
	}

	function addReplyButtons() {
		Array.prototype.forEach.call(document.querySelectorAll(".comment, .review-comment"), function (comment) {
			var oldReply = comment.querySelector(".GithubReplyComments, .GithubCommentEnhancerReply");
			if (oldReply) {
				oldReply.parentNode.removeChild(oldReply);
			}

			var header = comment.querySelector(":scope > :not(.minimized-comment) .timeline-comment-header"),
				actions = comment.querySelector(":scope > :not(.minimized-comment) .timeline-comment-actions");

			if (!header) {
				header = actions;
			}

			if (!actions) {
				if (!header) {
					return;
				}
				actions = document.createElement("div");
				actions.classList.add("timeline-comment-actions");
				header.insertBefore(actions, header.firstElementChild);
			}

			var reply = document.createElement("button");
			reply.setAttribute("type", "button");
			reply.setAttribute("title", "Reply to this comment");
			reply.setAttribute("aria-label", "Reply to this comment");
			reply.classList.add("GithubReplyComments", "btn-link", "timeline-comment-action", "tooltipped", "tooltipped-ne");
			reply.addEventListener("click", function (e) {
				e.preventDefault();

				var timestamp = comment.querySelector(".js-timestamp, .timestamp");

				var commentText = getCommentMarkdown(comment);
				commentText = commentText.trim().split("\n").map(function (line) {
					return "> " + line;
				}).join("\n");

				var newComment = getCommentTextarea(this);

				var author = comment.querySelector(".author");
				var authorLink = location.origin + (author.getAttribute("href") || "/" + author.textContent);

				var text = newComment.value.length > 0 ? "\n" : "";
				text += String.format('[**@{0}**]({1}) commented on [{2}]({3} "{4} - Replied by Github Reply Comments"):\n{5}\n\n',
					author.textContent,
					authorLink,
					timestamp.firstElementChild.getAttribute("title"),
					timestamp.href,
					timestamp.firstElementChild.getAttribute("datetime"),
					commentText);

				newComment.value += text;
				newComment.setSelectionRange(newComment.value.length, newComment.value.length);
				//newComment.closest('.previewable-comment-form').querySelector('.js-write-tab').click();
				newComment.focus();

				// This will enable the "Comment" button, when there was no comment text yet.
				newComment.dispatchEvent(new CustomEvent('change', {
					bubbles: true,
					cancelable: false
				}));

				// This will render GitHub Writer - https://github.com/ckeditor/github-writer
				// https://github.com/ckeditor/github-writer/blob/8dbc12cb01b7903d0d6c90202078214a8637de6d/src/app/plugins/quoteselection.js#L116-L127
				const githubWriter = newComment.closest([
					'form.js-new-comment-form[data-github-writer-id]',
					'form.js-inline-comment-form[data-github-writer-id]'
				].join());
				if (githubWriter) {
					window.postMessage({
						type: 'GitHub-Writer-Quote-Selection',
						id: Number(githubWriter.getAttribute('data-github-writer-id')),
						text: text
					}, '*');
				}
			});

			var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
			svg.classList.add("octicon", "octicon-mail-reply");
			svg.setAttribute("height", "16");
			svg.setAttribute("width", "16");
			reply.appendChild(svg);
			var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
			path.setAttribute("d", "M6 2.5l-6 4.5 6 4.5v-3c1.73 0 5.14 0.95 6 4.38 0-4.55-3.06-7.05-6-7.38v-3z");
			svg.appendChild(path);

			actions.appendChild(reply);
		});
	}

	// init;
	addReplyButtons();

	// on pjax;
	document.addEventListener('pjax:end', addReplyButtons);

})();