Greasy Fork 支持简体中文。

Github Comment Enhancer

Enhances Github comments

目前為 2014-06-01 提交的版本,檢視 最新版本

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