Github Comment Enhancer

Enhances Github comments

当前为 2014-05-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @id Github_Comment_Enhancer@https://github.com/jerone/UserScripts
  3. // @name Github Comment Enhancer
  4. // @namespace https://github.com/jerone/UserScripts
  5. // @description Enhances Github comments
  6. // @author jerone
  7. // @homepage https://github.com/jerone/UserScripts/tree/master/Github_Comment_Enhancer
  8. // @homepageURL https://github.com/jerone/UserScripts/tree/master/Github_Comment_Enhancer
  9. // @version 1.3
  10. // @grant none
  11. // @run-at document-end
  12. // @include https://github.com/*/*/issues/*
  13. // @include https://github.com/*/*/pull/*
  14. // @include https://github.com/*/*/commit/*
  15. // @include https://github.com/*/*/wiki/*
  16. // @include https://gist.github.com/*
  17. // ==/UserScript==
  18. /* global unsafeWindow */
  19.  
  20. (function() {
  21.  
  22. String.format = function(string) {
  23. var args = Array.prototype.slice.call(arguments, 1, arguments.length);
  24. return string.replace(/{(\d+)}/g, function(match, number) {
  25. return typeof args[number] !== "undefined" ? args[number] : match;
  26. });
  27. };
  28.  
  29. // Source: https://github.com/gollum/gollum/blob/9c714e768748db4560bc017cacef4afa0c751a63/lib/gollum/public/gollum/javascript/editor/langs/markdown.js
  30. var MarkDown = {
  31. "function-bold": {
  32. search: /^(\s*)([\s\S]*?)(\s*)$/g,
  33. replace: "$1**$2**$3"
  34. },
  35. "function-italic": {
  36. search: /^(\s*)([\s\S]*?)(\s*)$/g,
  37. replace: "$1_$2_$3"
  38. },
  39. "function-strikethrough": {
  40. search: /^(\s*)([\s\S]*?)(\s*)$/g,
  41. replace: "$1~~$2~~$3"
  42. },
  43.  
  44. "function-h1": {
  45. search: /(.+)([\n]?)/g,
  46. replace: "# $1$2",
  47. forceNewline: true
  48. },
  49. "function-h2": {
  50. search: /(.+)([\n]?)/g,
  51. replace: "## $1$2",
  52. forceNewline: true
  53. },
  54. "function-h3": {
  55. search: /(.+)([\n]?)/g,
  56. replace: "### $1$2",
  57. forceNewline: true
  58. },
  59. "function-h4": {
  60. search: /(.+)([\n]?)/g,
  61. replace: "#### $1$2",
  62. forceNewline: true
  63. },
  64. "function-h5": {
  65. search: /(.+)([\n]?)/g,
  66. replace: "##### $1$2",
  67. forceNewline: true
  68. },
  69. "function-h6": {
  70. search: /(.+)([\n]?)/g,
  71. replace: "###### $1$2",
  72. forceNewline: true
  73. },
  74.  
  75. "function-link": {
  76. exec: function(txt, selText, commentForm, next) {
  77. var selTxt = selText.trim(),
  78. isUrl = selTxt && /(?:https?:\/\/)|(?:www\.)/.test(selTxt),
  79. href = window.prompt("Link href:", isUrl ? selTxt : ""),
  80. text = window.prompt("Link text:", isUrl ? "" : selTxt);
  81. if (href) {
  82. next(String.format("![{0}]({1}){2}", text || href, href, (/\s+$/.test(selText) ? " " : "")));
  83. }
  84. }
  85. },
  86. "function-image": {
  87. exec: function(txt, selText, commentForm, next) {
  88. var selTxt = selText.trim(),
  89. isUrl = selTxt && /(?:https?:\/\/)|(?:www\.)/.test(selTxt),
  90. href = window.prompt("Image href:", isUrl ? selTxt : ""),
  91. text = window.prompt("Image text:", isUrl ? "" : selTxt);
  92. if (href) {
  93. next(String.format("![{0}]({1}){2}", text || href, href, (/\s+$/.test(selText) ? " " : "")));
  94. }
  95. }
  96. },
  97.  
  98. "function-ul": {
  99. search: /(.+)([\n]?)/g,
  100. replace: "* $1$2",
  101. forceNewline: true
  102. },
  103. "function-ol": {
  104. exec: function(txt, selText, commentForm, next) {
  105. var repText = "";
  106. if (!selText) {
  107. repText = "1. ";
  108. } else {
  109. var lines = selText.split("\n"),
  110. hasContent = /[\w]+/;
  111. for (var i = 0; i < lines.length; i++) {
  112. if (hasContent.test(lines[i])) {
  113. repText += String.format("$0. $1\n", i + 1, lines[i]);
  114. }
  115. }
  116. }
  117. next(repText);
  118. }
  119. },
  120. "function-checklist": {
  121. search: /(.+)([\n]?)/g,
  122. replace: "* [ ] $1$2",
  123. forceNewline: true
  124. },
  125.  
  126. "function-code": {
  127. exec: function(txt, selText, commentForm, next) {
  128. var rt = selText.indexOf("\n") > -1 ? "$1\n```\n$2\n```$3" : "$1`$2`$3";
  129. next(selText.replace(/^(\s*)([\s\S]*?)(\s*)$/g, rt));
  130. }
  131. },
  132. "function-blockquote": {
  133. search: /(.+)([\n]?)/g,
  134. replace: "> $1$2",
  135. forceNewline: true
  136. },
  137. "function-hr": {
  138. append: "\n***\n",
  139. forceNewline: true
  140. },
  141. "function-table": {
  142. append: "\n" +
  143. "| Head | Head | Head |\n" +
  144. "| :--- | :--: | ---: |\n" +
  145. "| Cell | Cell | Cell |\n" +
  146. "| Cell | Cell | Cell |\n",
  147. forceNewline: true
  148. },
  149.  
  150. "function-clear": {
  151. exec: function(txt, selText, commentForm, next) {
  152. commentForm.value = "";
  153. next("");
  154. }
  155. }
  156. };
  157.  
  158. var editorHTML = (function() {
  159. return '<div id="gollum-editor-function-buttons" style="float: left;">' +
  160. ' <div class="button-group">' +
  161. ' <a href="#" id="function-bold" class="minibutton function-button" title="Bold" tabindex="-1">' +
  162. ' <b>B</b>' +
  163. ' </a>' +
  164. ' <a href="#" id="function-italic" class="minibutton function-button" title="Italic" tabindex="-1">' +
  165. ' <em>i</em>' +
  166. ' </a>' +
  167. ' <a href="#" id="function-strikethrough" class="minibutton function-button" title="Strikethrough" tabindex="-1">' +
  168. ' <s>S</s>' +
  169. ' </a>' +
  170. ' </div>' +
  171.  
  172. ' <div class="button-group">' +
  173. ' <div class="select-menu js-menu-container js-select-menu js-composer-assignee-picker">' +
  174. ' <span aria-haspopup="true" title="Headers" role="button" class="minibutton select-menu-button icon-only js-menu-target" style="padding:0 18px 0 7px; width:auto; border-bottom-right-radius:3px; border-top-right-radius:3px;">' +
  175. ' <b>h#</b>' +
  176. ' </span>' +
  177. ' <div aria-hidden="false" class="select-menu-modal-holder js-menu-content js-navigation-container js-active-navigation-container" style="top: 26px;">' +
  178. ' <div class="select-menu-modal" style="width:auto;">' +
  179. ' <div class="select-menu-header">' +
  180. ' <span class="select-menu-title">Choose header</span>' +
  181. ' <span class="octicon octicon-remove-close js-menu-close"></span>' +
  182. ' </div>' +
  183. ' <div class="button-group">' +
  184. ' <a href="#" id="function-h1" class="minibutton function-button js-menu-close" title="Header 1" tabindex="-1">' +
  185. ' <b>h1</b>' +
  186. ' </a>' +
  187. ' <a href="#" id="function-h2" class="minibutton function-button js-menu-close" title="Header 2" tabindex="-1">' +
  188. ' <b>h2</b>' +
  189. ' </a>' +
  190. ' <a href="#" id="function-h3" class="minibutton function-button js-menu-close" title="Header 3" tabindex="-1">' +
  191. ' <b>h3</b>' +
  192. ' </a>' +
  193. ' <a href="#" id="function-h4" class="minibutton function-button js-menu-close" title="Header 4" tabindex="-1">' +
  194. ' <b>h4</b>' +
  195. ' </a>' +
  196. ' <a href="#" id="function-h5" class="minibutton function-button js-menu-close" title="Header 5" tabindex="-1">' +
  197. ' <b>h5</b>' +
  198. ' </a>' +
  199. ' <a href="#" id="function-h6" class="minibutton function-button js-menu-close" title="Header 6" tabindex="-1">' +
  200. ' <b>h6</b>' +
  201. ' </a>' +
  202. ' </div>' +
  203. ' </div>' +
  204. ' </div>' +
  205. ' </div>' +
  206. ' </div>' +
  207.  
  208. ' <div class="button-group">' +
  209. ' <a href="#" id="function-link" class="minibutton function-button" title="Link" tabindex="-1">' +
  210. ' <span class="octicon octicon-link"></span>' +
  211. ' </a>' +
  212. ' <a href="#" id="function-image" class="minibutton function-button" title="Image" tabindex="-1">' +
  213. ' <span class="octicon octicon-file-media"></span>' +
  214. ' </a>' +
  215. ' </div>' +
  216. ' <div class="button-group">' +
  217. ' <a href="#" id="function-ul" class="minibutton function-button" title="Unordered List" tabindex="-1">' +
  218. ' <span class="octicon octicon-list-unordered"></span>' +
  219. ' </a>' +
  220. ' <a href="#" id="function-ol" class="minibutton function-button" title="Ordered List" tabindex="-1">' +
  221. ' <span class="octicon octicon-list-ordered"></span>' +
  222. ' </a>' +
  223. ' <a href="#" id="function-checklist" class="minibutton function-button" title="Task List" tabindex="-1">' +
  224. ' <span class="octicon octicon-checklist"></span>' +
  225. ' </a>' +
  226. ' </div>' +
  227.  
  228. ' <div class="button-group">' +
  229. ' <a href="#" id="function-code" class="minibutton function-button" title="Code" tabindex="-1">' +
  230. ' <span class="octicon octicon-code"></span>' +
  231. ' </a>' +
  232. ' <a href="#" id="function-blockquote" class="minibutton function-button" title="Blockquote" tabindex="-1">' +
  233. ' <span class="octicon octicon-quote"></span>' +
  234. ' </a>' +
  235. ' <a href="#" id="function-hr" class="minibutton function-button" title="Horizontal Rule" tabindex="-1">' +
  236. ' <span class="octicon octicon-horizontal-rule"></span>' +
  237. ' </a>' +
  238. ' <a href="#" id="function-table" class="minibutton function-button" title="Table" tabindex="-1">' +
  239. ' <span class="octicon octicon-three-bars"></span>' +
  240. ' </a>' +
  241. ' </div>' +
  242. '</div>' +
  243.  
  244. '<div class="button-group" style="float:right;">' +
  245. ' <a href="#" id="function-clear" class="minibutton function-button" title="Clear" tabindex="-1">' +
  246. ' <span class="octicon octicon-circle-slash"></span>' +
  247. ' </a>' +
  248. '</div>';
  249. })();
  250.  
  251. // Source: https://github.com/gollum/gollum/blob/9c714e768748db4560bc017cacef4afa0c751a63/lib/gollum/public/gollum/javascript/editor/gollum.editor.js#L516
  252. function executeAction(definitionObject, commentForm) {
  253. var txt = commentForm.value,
  254. selPos = {
  255. start: commentForm.selectionStart,
  256. end: commentForm.selectionEnd
  257. },
  258. selText = txt.substring(selPos.start, selPos.end),
  259. repText = selText,
  260. reselect = true,
  261. cursor = null;
  262.  
  263. // execute replacement function;
  264. if (definitionObject.exec) {
  265. definitionObject.exec(txt, selText, commentForm, function(repText) {
  266. replaceFieldSelection(commentForm, repText);
  267. });
  268. return;
  269. }
  270.  
  271. // execute a search;
  272. var searchExp = new RegExp(definitionObject.search || /([^\n]+)/gi);
  273.  
  274. // replace text;
  275. if (definitionObject.replace) {
  276. var rt = definitionObject.replace;
  277. repText = repText.replace(searchExp, rt);
  278. repText = repText.replace(/\$[\d]/g, "");
  279. if (repText === "") {
  280. cursor = rt.indexOf("$1");
  281. repText = rt.replace(/\$[\d]/g, "");
  282. if (cursor === -1) {
  283. cursor = Math.floor(rt.length / 2);
  284. }
  285. }
  286. }
  287.  
  288. // append if necessary;
  289. if (definitionObject.append) {
  290. if (repText === selText) {
  291. reselect = false;
  292. }
  293. repText += definitionObject.append;
  294. }
  295.  
  296. if (repText) {
  297. if (definitionObject.forceNewline === true && (selPos.start > 0 && txt.substr(Math.max(0, selPos.start - 1), 1) !== "\n")) {
  298. repText = "\n" + repText;
  299. }
  300. replaceFieldSelection(commentForm, repText, reselect, cursor);
  301. }
  302. }
  303.  
  304. // Source: https://github.com/gollum/gollum/blob/9c714e768748db4560bc017cacef4afa0c751a63/lib/gollum/public/gollum/javascript/editor/gollum.editor.js#L708
  305. function replaceFieldSelection(commentForm, replaceText, reselect, cursorOffset) {
  306. var txt = commentForm.value,
  307. selPos = {
  308. start: commentForm.selectionStart,
  309. end: commentForm.selectionEnd
  310. };
  311.  
  312. var selectNew = true;
  313. if (reselect === false) {
  314. selectNew = false;
  315. }
  316.  
  317. var scrollTop = null;
  318. if (commentForm.scrollTop) {
  319. scrollTop = commentForm.scrollTop;
  320. }
  321.  
  322. commentForm.value = txt.substring(0, selPos.start) + replaceText + txt.substring(selPos.end);
  323. commentForm.focus();
  324.  
  325. // Gist Github requires that the comment form change event be triggered to update the preview;
  326. unsafeWindow.$(commentForm).trigger("change");
  327.  
  328. if (selectNew) {
  329. if (cursorOffset) {
  330. commentForm.setSelectionRange(selPos.start + cursorOffset, selPos.start + cursorOffset);
  331. } else {
  332. commentForm.setSelectionRange(selPos.start, selPos.start + replaceText.length);
  333. }
  334. }
  335.  
  336. if (scrollTop) {
  337. commentForm.scrollTop = scrollTop;
  338. }
  339. }
  340.  
  341. function isWiki() {
  342. return /\/wiki\//.test(location.href);
  343. }
  344.  
  345. var functionButtonClick = function(e) {
  346. e.preventDefault();
  347. executeAction(MarkDown[this.id], this.commentForm);
  348. return false;
  349. };
  350.  
  351. function addToolbar() {
  352. if (isWiki()) {
  353. // Override existing language with improved & missing functions and remove existing click events;
  354. unsafeWindow.$.GollumEditor.defineLanguage("markdown", MarkDown);
  355. unsafeWindow.$(".function-button:not(#function-help)").unbind("click");
  356.  
  357. // Remove existing click events when changing languages;
  358. document.getElementById("wiki_format").addEventListener("change", function() {
  359. unsafeWindow.$(".function-button:not(#function-help)").unbind("click");
  360.  
  361. Array.forEach(document.querySelectorAll(".comment-form-textarea .function-button"), function(button) {
  362. button.removeEventListener("click", functionButtonClick);
  363. });
  364. });
  365. }
  366.  
  367. Array.forEach(document.querySelectorAll(".comment-form-textarea,.js-comment-field"), function(commentForm) {
  368. if (commentForm.classList.contains("GithubCommentEnhancer")) { return; }
  369. commentForm.classList.add("GithubCommentEnhancer");
  370.  
  371. var gollumEditor;
  372. if (isWiki()) {
  373. gollumEditor = document.getElementById("gollum-editor-function-bar");
  374. var temp = document.createElement("div");
  375. temp.innerHTML = editorHTML;
  376. temp.firstChild.appendChild(document.getElementById("function-help")); // restore the help button;
  377. gollumEditor.replaceChild(temp.querySelector("#gollum-editor-function-buttons"), document.getElementById("gollum-editor-function-buttons"));
  378. Array.forEach(temp.children, function(elm) {
  379. elm.style.position = "absolute";
  380. elm.style.right = "30px";
  381. elm.style.top = "0";
  382. commentForm.parentNode.insertBefore(elm, commentForm);
  383. });
  384. temp = null;
  385. } else {
  386. gollumEditor = document.createElement("div");
  387. gollumEditor.innerHTML = editorHTML;
  388. gollumEditor.id = "gollum-editor-function-bar";
  389. gollumEditor.style.border = "0 none";
  390. gollumEditor.style.height = "26px";
  391. gollumEditor.style.margin = "10px 0";
  392. gollumEditor.style.paddingBottom = "10px";
  393. gollumEditor.classList.add("active");
  394. commentForm.parentNode.insertBefore(gollumEditor, commentForm);
  395. }
  396.  
  397. Array.forEach(gollumEditor.parentNode.querySelectorAll(".function-button"), function(button) {
  398. button.style.padding = "0px";
  399. button.style.textAlign = "center";
  400. button.style.width = "30px";
  401. button.firstElementChild.style.marginRight = "0px";
  402. button.commentForm = commentForm; // remove event listener doesn't accept `bind`;
  403. button.addEventListener("click", functionButtonClick);
  404. });
  405. });
  406. }
  407.  
  408. // init;
  409. addToolbar();
  410.  
  411. // on pjax;
  412. unsafeWindow.$(document).on("pjax:success", addToolbar);
  413.  
  414. // on page update;
  415. unsafeWindow.$.pageUpdate(function() {
  416. window.setTimeout(function() {
  417. addToolbar();
  418. }, 1);
  419. });
  420.  
  421. })();

QingJ © 2025

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