- // ==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.6
- // @icon https://github.githubassets.com/pinned-octocat.svg
- // @grant none
- // @include https://github.com/*
- // @include https://gist.github.com/*
- // @require https://unpkg.com/turndown@5.0.3/dist/turndown.js
- // @require https://unpkg.com/turndown-plugin-gfm@1.0.2/dist/turndown-plugin-gfm.js
- // @require https://unpkg.com/turndown-plugin-github-code-snippet@1.0.2/turndown-plugin-github-code-snippet.js
- // ==/UserScript==
-
- // cSpell:ignore textareas, previewable, tooltipped
- /* eslint security/detect-object-injection: "off" */
- /* global TurndownService,turndownPluginGfm,turndownPluginGithubCodeSnippet */
-
- (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;
- });
- };
-
- function turndownPluginGitHubAlert(turndownService) {
- turndownService.addRule("gfm-alert", {
- filter: function (node, _options) {
- return (
- node.nodeName === "DIV" &&
- node.classList.contains("markdown-alert")
- );
- },
- replacement: function (content, node, options) {
- const variant = node
- .querySelector(".markdown-alert-title")
- .innerText.trim();
- content = content.replace(/^\n+|\n+$/g, "");
- content = content.replace(
- // eslint-disable-next-line security/detect-non-literal-regexp
- new RegExp("^" + variant),
- "[!" + variant.toUpperCase() + "]",
- );
- return options.rules.blockquote.replacement(content);
- },
- });
- }
-
- var turndownService = new TurndownService({
- headingStyle: "atx",
- codeBlockStyle: "fenced",
- hr: "***",
- });
- turndownService.use(turndownPluginGfm.gfm);
- turndownService.use(turndownPluginGithubCodeSnippet);
- turndownService.use(turndownPluginGitHubAlert);
-
- 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) .js-comment-field:not(.github-writer-ckeditor)",
- );
- return textareas[textareas.length - 1];
- }
-
- function getCommentMarkdown(comment) {
- var commentText = "";
-
- // Use raw comment when available.
- // Extra scope is needed to get the correct comment field, which is not an "Reference new issue" modal (with org rights).
- var commentForm = comment.querySelector(
- ":scope > .js-comment-update .js-comment-field",
- );
- if (commentForm) {
- commentText = commentForm.value;
- }
-
- // Convert comment HTML to markdown.
- if (!commentText) {
- // Clone it, so we can alter the HTML a bit, without modifying the page.
- var commentBody = comment
- .querySelector(".comment-body")
- .cloneNode(true);
-
- // Skip empty PR description.
- if (
- commentBody
- .querySelector("em")
- ?.innerText.includes("No description provided.")
- ) {
- return "";
- }
-
- // Remove 'Toggle code wrap' buttons from https://gf.qytechs.cn/en/scripts/18789-github-toggle-code-wrap
- Array.prototype.forEach.call(
- commentBody.querySelectorAll(".ghd-wrap-toggle"),
- function (ghd) {
- ghd.remove();
- },
- );
-
- // Refined GitHub adds a small avatar to username mention. See https://github.com/refined-github/refined-github/blob/main/source/features/small-user-avatars.tsx
- Array.prototype.forEach.call(
- commentBody.querySelectorAll(".rgh-small-user-avatars"),
- function (rgh) {
- rgh.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);
- })();