Github Comment Enhancer

Enhances Github comments

当前为 2015-04-05 提交的版本,查看 最新版本

  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. // @supportURL https://github.com/jerone/UserScripts/issues
  12. // @contributionURL https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCYMHWQ7ZMBKW
  13. // @version 2.3.0
  14. // @grant none
  15. // @run-at document-end
  16. // @include https://github.com/*/*/issues
  17. // @include https://github.com/*/*/issues/*
  18. // @include https://github.com/*/*/pulls
  19. // @include https://github.com/*/*/pull/*
  20. // @include https://github.com/*/*/commit/*
  21. // @include https://github.com/*/*/compare/*
  22. // @include https://github.com/*/*/wiki/*
  23. // @include https://gist.github.com/*
  24. // ==/UserScript==
  25. /* global unsafeWindow */
  26.  
  27. (function(unsafeWindow) {
  28.  
  29. String.format = function(string) {
  30. var args = Array.prototype.slice.call(arguments, 1, arguments.length);
  31. return string.replace(/{(\d+)}/g, function(match, number) {
  32. return typeof args[number] !== "undefined" ? args[number] : match;
  33. });
  34. };
  35.  
  36. // Choose the character that precedes the list;
  37. var listCharacter = ["*", "-", "+"][0];
  38.  
  39. // Choose the characters that makes up a horizontal line;
  40. var lineCharacter = ["***", "---", "___"][0];
  41.  
  42. // Source: https://github.com/gollum/gollum/blob/9c714e768748db4560bc017cacef4afa0c751a63/lib/gollum/public/gollum/javascript/editor/langs/markdown.js
  43. var MarkDown = (function MarkDown() {
  44. return {
  45. "function-bold": {
  46. search: /^(\s*)([\s\S]*?)(\s*)$/g,
  47. replace: "$1**$2**$3"
  48. },
  49. "function-italic": {
  50. search: /^(\s*)([\s\S]*?)(\s*)$/g,
  51. replace: "$1_$2_$3"
  52. },
  53. "function-underline": {
  54. search: /^(\s*)([\s\S]*?)(\s*)$/g,
  55. replace: "$1<ins>$2</ins>$3"
  56. },
  57. "function-strikethrough": {
  58. search: /^(\s*)([\s\S]*?)(\s*)$/g,
  59. replace: "$1~~$2~~$3"
  60. },
  61.  
  62. "function-h1": {
  63. search: /(.+)([\n]?)/g,
  64. replace: "# $1$2",
  65. forceNewline: true
  66. },
  67. "function-h2": {
  68. search: /(.+)([\n]?)/g,
  69. replace: "## $1$2",
  70. forceNewline: true
  71. },
  72. "function-h3": {
  73. search: /(.+)([\n]?)/g,
  74. replace: "### $1$2",
  75. forceNewline: true
  76. },
  77. "function-h4": {
  78. search: /(.+)([\n]?)/g,
  79. replace: "#### $1$2",
  80. forceNewline: true
  81. },
  82. "function-h5": {
  83. search: /(.+)([\n]?)/g,
  84. replace: "##### $1$2",
  85. forceNewline: true
  86. },
  87. "function-h6": {
  88. search: /(.+)([\n]?)/g,
  89. replace: "###### $1$2",
  90. forceNewline: true
  91. },
  92.  
  93. "function-link": {
  94. exec: function(button, selText, commentForm, next) {
  95. var selTxt = selText.trim(),
  96. isUrl = selTxt && /(?:https?:\/\/)|(?:www\.)/.test(selTxt),
  97. href = window.prompt("Link href:", isUrl ? selTxt : ""),
  98. text = window.prompt("Link text:", isUrl ? "" : selTxt);
  99. if (href) {
  100. next(String.format("[{0}]({1}){2}", text || href, href, (/\s+$/.test(selText) ? " " : "")));
  101. }
  102. }
  103. },
  104. "function-image": {
  105. exec: function(button, selText, commentForm, next) {
  106. var selTxt = selText.trim(),
  107. isUrl = selTxt && /(?:https?:\/\/)|(?:www\.)/.test(selTxt),
  108. href = window.prompt("Image href:", isUrl ? selTxt : ""),
  109. text = window.prompt("Image text:", isUrl ? "" : selTxt);
  110. if (href) {
  111. next(String.format("![{0}]({1}){2}", text || href, href, (/\s+$/.test(selText) ? " " : "")));
  112. }
  113. }
  114. },
  115.  
  116. "function-ul": {
  117. search: /(.+)([\n]?)/g,
  118. replace: String.format("{0} $1$2", listCharacter),
  119. forceNewline: true
  120. },
  121. "function-ol": {
  122. exec: function(button, selText, commentForm, next) {
  123. var repText = "";
  124. if (!selText) {
  125. repText = "1. ";
  126. } else {
  127. var lines = selText.split("\n"),
  128. hasContent = /[\w]+/;
  129. for (var i = 0; i < lines.length; i++) {
  130. if (hasContent.test(lines[i])) {
  131. repText += String.format("{0}. {1}\n", i + 1, lines[i]);
  132. }
  133. }
  134. }
  135. next(repText);
  136. }
  137. },
  138. "function-checklist": {
  139. search: /(.+)([\n]?)/g,
  140. replace: String.format("{0} [ ] $1$2", listCharacter),
  141. forceNewline: true
  142. },
  143.  
  144. "function-code": {
  145. exec: function(button, selText, commentForm, next) {
  146. var rt = selText.indexOf("\n") > -1 ? "$1\n```\n$2\n```$3" : "$1`$2`$3";
  147. next(selText.replace(/^(\s*)([\s\S]*?)(\s*)$/g, rt));
  148. }
  149. },
  150. "function-blockquote": {
  151. search: /(.+)([\n]?)/g,
  152. replace: "> $1$2",
  153. forceNewline: true
  154. },
  155. "function-hr": {
  156. append: String.format("\n{0}\n", lineCharacter),
  157. forceNewline: true
  158. },
  159. "function-table": {
  160. append: "\n" +
  161. "| Head | Head | Head |\n" +
  162. "| :--- | :----: | ----: |\n" +
  163. "| Cell | Cell | Cell |\n" +
  164. "| Left | Center | Right |\n",
  165. forceNewline: true
  166. },
  167.  
  168. "function-clear": {
  169. exec: function(button, selText, commentForm, next) {
  170. commentForm.value = "";
  171. next("");
  172. }
  173. },
  174.  
  175. "function-snippets-tab": {
  176. exec: function(button, selText, commentForm, next) {
  177. next("\t");
  178. }
  179. },
  180. "function-snippets-useragent": {
  181. exec: function(button, selText, commentForm, next) {
  182. next("`" + navigator.userAgent + "`");
  183. }
  184. },
  185. "function-snippets-contributing": {
  186. exec: function(button, selText, commentForm, next) {
  187. next("Please, always consider reviewing the [guidelines for contributing](../blob/master/CONTRIBUTING.md) to this repository.");
  188. }
  189. },
  190.  
  191. "function-emoji": {
  192. exec: function(button, selText, commentForm, next) {
  193. next(":" + button.dataset.value + ":");
  194. }
  195. }
  196. };
  197. })();
  198.  
  199. var editorHTML = (function editorHTML() {
  200. return '<div id="gollum-editor-function-buttons" style="float: left;">' +
  201. ' <div class="button-group btn-group">' +
  202. ' <a href="#" id="function-bold" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Bold" style="height:26px;">' +
  203. ' <b style="font-weight: bolder;">B</b>' +
  204. ' </a>' +
  205. ' <a href="#" id="function-italic" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Italic">' +
  206. ' <em>i</em>' +
  207. ' </a>' +
  208. ' <a href="#" id="function-underline" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Underline">' +
  209. ' <ins>U</ins>' +
  210. ' </a>' +
  211. ' <a href="#" id="function-strikethrough" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Strikethrough">' +
  212. ' <s>S</s>' +
  213. ' </a>' +
  214. ' </div>' +
  215.  
  216. ' <div class="button-group btn-group">' +
  217. ' <div class="select-menu js-menu-container js-select-menu tooltipped tooltipped-ne" aria-label="Headers">' +
  218. ' <span class="btn btn-sm minibutton select-menu-button icon-only js-menu-target" aria-label="Headers" style="padding-left:7px; padding-right:7px; width:auto; border-bottom-right-radius:3px; border-top-right-radius:3px;">' +
  219. ' <span class="js-select-button">h#</span>' +
  220. ' </span>' +
  221. ' <div class="select-menu-modal-holder js-menu-content js-navigation-container js-active-navigation-container" style="top: 26px;">' +
  222. ' <div class="select-menu-modal" style="width:auto; overflow:visible;">' +
  223. ' <div class="select-menu-header">' +
  224. ' <span class="select-menu-title">Choose header</span>' +
  225. ' <span class="octicon octicon-remove-close js-menu-close"></span>' +
  226. ' </div>' +
  227. ' <div class="button-group btn-group">' +
  228. ' <a href="#" id="function-h1" class="btn btn-sm minibutton function-button js-navigation-item js-menu-close tooltipped tooltipped-s" aria-label="Header 1">' +
  229. ' <b class="select-menu-item-text js-select-button-text">h1</b>' +
  230. ' </a>' +
  231. ' <a href="#" id="function-h2" class="btn btn-sm minibutton function-button js-navigation-item js-menu-close tooltipped tooltipped-s" aria-label="Header 2">' +
  232. ' <b class="select-menu-item-text js-select-button-text">h2</b>' +
  233. ' </a>' +
  234. ' <a href="#" id="function-h3" class="btn btn-sm minibutton function-button js-navigation-item js-menu-close tooltipped tooltipped-s" aria-label="Header 3">' +
  235. ' <b class="select-menu-item-text js-select-button-text">h3</b>' +
  236. ' </a>' +
  237. ' <a href="#" id="function-h4" class="btn btn-sm minibutton function-button js-navigation-item js-menu-close tooltipped tooltipped-s" aria-label="Header 4">' +
  238. ' <b class="select-menu-item-text js-select-button-text">h4</b>' +
  239. ' </a>' +
  240. ' <a href="#" id="function-h5" class="btn btn-sm minibutton function-button js-navigation-item js-menu-close tooltipped tooltipped-s" aria-label="Header 5">' +
  241. ' <b class="select-menu-item-text js-select-button-text">h5</b>' +
  242. ' </a>' +
  243. ' <a href="#" id="function-h6" class="btn btn-sm minibutton function-button js-navigation-item js-menu-close tooltipped tooltipped-s" aria-label="Header 6">' +
  244. ' <b class="select-menu-item-text js-select-button-text">h6</b>' +
  245. ' </a>' +
  246. ' </div>' +
  247. ' </div>' +
  248. ' </div>' +
  249. ' </div>' +
  250. ' </div>' +
  251.  
  252. ' <div class="button-group btn-group">' +
  253. ' <a href="#" id="function-link" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Link">' +
  254. ' <span class="octicon octicon-link"></span>' +
  255. ' </a>' +
  256. ' <a href="#" id="function-image" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Image">' +
  257. ' <span class="octicon octicon-file-media"></span>' +
  258. ' </a>' +
  259. ' </div>' +
  260. ' <div class="button-group btn-group">' +
  261. ' <a href="#" id="function-ul" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Unordered List">' +
  262. ' <span class="octicon octicon-list-unordered"></span>' +
  263. ' </a>' +
  264. ' <a href="#" id="function-ol" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Ordered List">' +
  265. ' <span class="octicon octicon-list-ordered"></span>' +
  266. ' </a>' +
  267. ' <a href="#" id="function-checklist" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Task List">' +
  268. ' <span class="octicon octicon-checklist"></span>' +
  269. ' </a>' +
  270. ' </div>' +
  271.  
  272. ' <div class="button-group btn-group">' +
  273. ' <a href="#" id="function-code" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Code">' +
  274. ' <span class="octicon octicon-code"></span>' +
  275. ' </a>' +
  276. ' <a href="#" id="function-blockquote" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Blockquote">' +
  277. ' <span class="octicon octicon-quote"></span>' +
  278. ' </a>' +
  279. ' <a href="#" id="function-hr" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Horizontal Rule">' +
  280. ' <span class="octicon octicon-horizontal-rule"></span>' +
  281. ' </a>' +
  282. ' <a href="#" id="function-table" class="btn btn-sm minibutton function-button tooltipped tooltipped-ne" aria-label="Table">' +
  283. ' <span class="octicon octicon-three-bars"></span>' +
  284. ' </a>' +
  285. ' </div>' +
  286.  
  287. ' <div class="button-group btn-group">' +
  288. ' <div class="select-menu js-menu-container js-select-menu tooltipped tooltipped-ne" aria-label="Snippets">' +
  289. ' <span class="btn btn-sm minibutton select-menu-button js-menu-target" aria-label="Snippets" style="padding-left:7px; padding-right:7px; width:auto; border-bottom-right-radius:3px; border-top-right-radius:3px;">' +
  290. ' <span class="octicon octicon-pin"></span>' +
  291. ' </span>' +
  292. ' <div class="select-menu-modal-holder js-menu-content js-navigation-container js-active-navigation-container">' +
  293. ' <div class="select-menu-modal" style="overflow:visible;">' +
  294. ' <div class="select-menu-header">' +
  295. ' <span class="select-menu-title">Snippets</span>' +
  296. ' <span class="octicon octicon-remove-close js-menu-close"></span>' +
  297. ' </div>' +
  298. ' <div class="select-menu-filters">' +
  299. ' <div class="select-menu-text-filter">' +
  300. ' <input type="text" placeholder="Filter snippets..." class="js-filterable-field js-navigation-enable" id="context-snippets-filter-field">' +
  301. ' </div>' +
  302. ' </div>' +
  303. ' <div class="select-menu-list" style="overflow:visible;">' +
  304. ' <div data-filterable-type="substring" data-filterable-for="context-snippets-filter-field">' +
  305. ' <a href="#" id="function-snippets-tab" class="function-button select-menu-item js-navigation-item tooltipped tooltipped-w" aria-label="Add tab character" style="table-layout:initial;">' +
  306. ' <span class="select-menu-item-text js-select-button-text">Add tab character</span>' +
  307. ' </a>' +
  308. ' <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;">' +
  309. ' <span class="select-menu-item-text js-select-button-text">Add UserAgent</span>' +
  310. ' </a>' +
  311. ' <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;">' +
  312. ' <span class="select-menu-item-text">' +
  313. ' <span class="js-select-button-text">Contributing</span>' +
  314. ' <span class="description">Add contributing message</span>' +
  315. ' </span>' +
  316. ' </a>' +
  317. ' </div>' +
  318. ' <div class="select-menu-no-results">Nothing to show</div>' +
  319. ' </div>' +
  320. ' </div>' +
  321. ' </div>' +
  322. ' </div>' +
  323. ' </div>' +
  324.  
  325. ' <div class="button-group btn-group">' +
  326. ' <div class="select-menu js-menu-container js-select-menu tooltipped tooltipped-ne" aria-label="Emoji">' +
  327. ' <span class="btn btn-sm minibutton select-menu-button js-menu-target" aria-label="Emoji" style="padding-left:7px; padding-right:7px; width:auto; border-bottom-right-radius:3px; border-top-right-radius:3px;">' +
  328. ' <span class="octicon octicon-octoface"></span>' +
  329. ' </span>' +
  330. ' <div class="select-menu-modal-holder js-menu-content js-navigation-container js-active-navigation-container">' +
  331. ' <div class="select-menu-modal" style="overflow:visible;">' +
  332. ' <div class="select-menu-header">' +
  333. ' <span class="select-menu-title">Emoji</span>' +
  334. ' <span class="octicon octicon-remove-close js-menu-close"></span>' +
  335. ' </div>' +
  336. ' <div class="select-menu-filters">' +
  337. ' <div class="select-menu-text-filter">' +
  338. ' <input type="text" placeholder="Filter emoji..." class="js-filterable-field js-navigation-enable" id="context-emoji-filter-field">' +
  339. ' </div>' +
  340. ' </div>' +
  341. ' <div class="suggester select-menu-list" style="overflow:visible;">' +
  342. ' <div class="select-menu-no-results">Nothing to show</div>' +
  343. ' </div>' +
  344. ' </div>' +
  345. ' </div>' +
  346. ' </div>' +
  347. ' </div>' +
  348.  
  349. '</div>' +
  350.  
  351. '<div style="float:right;">' +
  352. ' <a href="#" id="function-clear" class="btn btn-sm minibutton function-button tooltipped tooltipped-nw" aria-label="Clear">' +
  353. ' <span class="octicon octicon-circle-slash"></span>' +
  354. ' </a>' +
  355. '</div>';
  356. })();
  357.  
  358. // Source: https://github.com/gollum/gollum/blob/9c714e768748db4560bc017cacef4afa0c751a63/lib/gollum/public/gollum/javascript/editor/gollum.editor.js#L516
  359. function executeAction(definitionObject, commentForm, button) {
  360. var txt = commentForm.value,
  361. selPos = {
  362. start: commentForm.selectionStart,
  363. end: commentForm.selectionEnd
  364. },
  365. selText = txt.substring(selPos.start, selPos.end),
  366. repText = selText,
  367. reselect = true,
  368. cursor = null;
  369.  
  370. // execute replacement function;
  371. if (definitionObject.exec) {
  372. definitionObject.exec(button, selText, commentForm, function(repText) {
  373. replaceFieldSelection(commentForm, repText);
  374. });
  375. return;
  376. }
  377.  
  378. // execute a search;
  379. var searchExp = new RegExp(definitionObject.search || /([^\n]+)/gi);
  380.  
  381. // replace text;
  382. if (definitionObject.replace) {
  383. var rt = definitionObject.replace;
  384. repText = repText.replace(searchExp, rt);
  385. repText = repText.replace(/\$[\d]/g, "");
  386. if (repText === "") {
  387. cursor = rt.indexOf("$1");
  388. repText = rt.replace(/\$[\d]/g, "");
  389. if (cursor === -1) {
  390. cursor = Math.floor(rt.length / 2);
  391. }
  392. }
  393. }
  394.  
  395. // append if necessary;
  396. if (definitionObject.append) {
  397. if (repText === selText) {
  398. reselect = false;
  399. }
  400. repText += definitionObject.append;
  401. }
  402.  
  403. if (repText) {
  404. if (definitionObject.forceNewline === true && (selPos.start > 0 && txt.substr(Math.max(0, selPos.start - 1), 1) !== "\n")) {
  405. repText = "\n" + repText;
  406. }
  407. replaceFieldSelection(commentForm, repText, reselect, cursor);
  408. }
  409. }
  410.  
  411. // Source: https://github.com/gollum/gollum/blob/9c714e768748db4560bc017cacef4afa0c751a63/lib/gollum/public/gollum/javascript/editor/gollum.editor.js#L708
  412. function replaceFieldSelection(commentForm, replaceText, reselect, cursorOffset) {
  413. var txt = commentForm.value,
  414. selPos = {
  415. start: commentForm.selectionStart,
  416. end: commentForm.selectionEnd
  417. };
  418.  
  419. var selectNew = true;
  420. if (reselect === false) {
  421. selectNew = false;
  422. }
  423.  
  424. var scrollTop = null;
  425. if (commentForm.scrollTop) {
  426. scrollTop = commentForm.scrollTop;
  427. }
  428.  
  429. commentForm.value = txt.substring(0, selPos.start) + replaceText + txt.substring(selPos.end);
  430. commentForm.focus();
  431.  
  432. if (selectNew) {
  433. if (cursorOffset) {
  434. commentForm.setSelectionRange(selPos.start + cursorOffset, selPos.start + cursorOffset);
  435. } else {
  436. commentForm.setSelectionRange(selPos.start, selPos.start + replaceText.length);
  437. }
  438. }
  439.  
  440. if (scrollTop) {
  441. commentForm.scrollTop = scrollTop;
  442. }
  443. }
  444.  
  445. function isWiki() {
  446. return /\/wiki\//.test(location.href);
  447. }
  448.  
  449. function isGist() {
  450. return location.host === "gist.github.com";
  451. }
  452.  
  453. function overrideGollumMarkdown() {
  454. unsafeWindow.$.GollumEditor.defineLanguage("markdown", MarkDown);
  455. }
  456.  
  457. function unbindGollumFunctions() {
  458. window.setTimeout(function() {
  459. unsafeWindow.$(".function-button:not(#function-help)").unbind("click");
  460. }, 1);
  461. }
  462.  
  463. var functionButtonClick = function(e) {
  464. e.preventDefault();
  465. executeAction(MarkDown[this.id], this.commentForm, this);
  466. return false;
  467. };
  468.  
  469. var suggestionsCache = {};
  470.  
  471. function addSuggestions(commentForm) {
  472. var jssuggester = commentForm.parentNode.parentNode.querySelector(".suggester-container .suggester");
  473. var url = jssuggester.getAttribute("data-url");
  474.  
  475. if (suggestionsCache[url]) {
  476. parseSuggestions(suggestionsCache[url]);
  477. } else {
  478. unsafeWindow.$.ajax({
  479. url: url,
  480. success: function(suggestionsData) {
  481. suggestionsCache[url] = suggestionsData
  482. parseSuggestions(commentForm, suggestionsData);
  483. }
  484. });
  485. }
  486. }
  487.  
  488. function parseSuggestions(commentForm, suggestionsData) {
  489. suggestionsData = suggestionsData.replace(/js-navigation-item/g, "function-button js-navigation-item select-menu-item");
  490.  
  491. var suggestions = document.createElement("div");
  492. suggestions.innerHTML = suggestionsData;
  493.  
  494. var emojiSuggestions = suggestions.querySelector(".emoji-suggestions");
  495. emojiSuggestions.style.display = "block";
  496. emojiSuggestions.dataset.filterableType = "substring";
  497. emojiSuggestions.dataset.filterableFor = "context-emoji-filter-field";
  498. emojiSuggestions.dataset.filterableLimit = "10";
  499.  
  500. var suggester = commentForm.parentNode.querySelector(".suggester");
  501. suggester.style.display = "block";
  502. suggester.style.marginTop = "0";
  503. suggester.appendChild(emojiSuggestions);
  504. Array.prototype.forEach.call(suggester.querySelectorAll(".function-button"), function(button) {
  505. button.addEventListener("click", function(e) {
  506. e.preventDefault();
  507. executeAction(MarkDown["function-emoji"], commentForm, this);
  508. return false;
  509. });
  510. });
  511. }
  512.  
  513. function addToolbar() {
  514. if (isWiki()) {
  515. // Override existing language with improved & missing functions and remove existing click events;
  516. overrideGollumMarkdown();
  517. unbindGollumFunctions();
  518.  
  519. // Remove existing click events when changing languages;
  520. document.getElementById("wiki_format").addEventListener("change", function() {
  521. unbindGollumFunctions();
  522.  
  523. Array.prototype.forEach.call(document.querySelectorAll(".comment-form-textarea .function-button"), function(button) {
  524. button.removeEventListener("click", functionButtonClick);
  525. });
  526. });
  527. }
  528.  
  529. Array.prototype.forEach.call(document.querySelectorAll(".comment-form-textarea,.js-comment-field"), function(commentForm) {
  530. var gollumEditor;
  531. if (commentForm.classList.contains("GithubCommentEnhancer")) {
  532. gollumEditor = commentForm.previousSibling;
  533. } else {
  534. commentForm.classList.add("GithubCommentEnhancer");
  535.  
  536. if (isWiki()) {
  537. gollumEditor = document.getElementById("gollum-editor-function-bar");
  538. var temp = document.createElement("div");
  539. temp.innerHTML = editorHTML;
  540. temp.firstElementChild.appendChild(document.getElementById("function-help")); // restore the help button;
  541. gollumEditor.replaceChild(temp.querySelector("#gollum-editor-function-buttons"), document.getElementById("gollum-editor-function-buttons"));
  542. Array.prototype.forEach.call(temp.children, function(elm) {
  543. elm.style.position = "absolute";
  544. elm.style.right = "30px";
  545. elm.style.top = "0";
  546. commentForm.parentNode.insertBefore(elm, commentForm);
  547. });
  548. temp = null;
  549. } else {
  550. gollumEditor = document.createElement("div");
  551. gollumEditor.innerHTML = editorHTML;
  552. gollumEditor.id = "gollum-editor-function-bar";
  553. gollumEditor.style.height = "26px";
  554. gollumEditor.style.margin = "10px 0";
  555. gollumEditor.classList.add("active");
  556. commentForm.parentNode.insertBefore(gollumEditor, commentForm);
  557. }
  558.  
  559. addSuggestions(commentForm);
  560.  
  561. var tabnavExtras = commentForm.parentNode.parentNode.querySelector(".comment-form-head .tabnav-right, .comment-form-head .right");
  562. if (tabnavExtras) {
  563. var sponsored = document.createElement("a");
  564. sponsored.setAttribute("target", "_blank");
  565. sponsored.setAttribute("href", "https://github.com/jerone/UserScripts/tree/master/Github_Comment_Enhancer");
  566. sponsored.classList.add("tabnav-widget", "text", "tabnav-extras", "tabnav-extra");
  567. var sponsoredIcon = document.createElement("span");
  568. sponsoredIcon.classList.add("octicon", "octicon-question");
  569. sponsored.appendChild(sponsoredIcon);
  570. sponsored.appendChild(document.createTextNode("Enhanced by Github Comment Enhancer"));
  571. tabnavExtras.insertBefore(sponsored, tabnavExtras.firstElementChild);
  572. }
  573. }
  574.  
  575. if (isGist()) {
  576. Array.prototype.forEach.call(gollumEditor.parentNode.querySelectorAll(".select-menu-button"), function(button) {
  577. button.style.paddingRight = "25px";
  578. });
  579. }
  580.  
  581. Array.prototype.forEach.call(gollumEditor.parentNode.querySelectorAll(".function-button"), function(button) {
  582. if (isGist() && button.classList.contains("minibutton")) {
  583. button.style.padding = "0px";
  584. button.style.textAlign = "center";
  585. button.style.width = "30px";
  586. button.firstElementChild.style.marginRight = "0px";
  587. }
  588. button.commentForm = commentForm; // remove event listener doesn't accept `bind`;
  589. button.addEventListener("click", functionButtonClick);
  590. });
  591. });
  592. }
  593.  
  594. /*
  595. * to-markdown - an HTML to Markdown converter
  596. * Copyright 2011, Dom Christie
  597. * Licenced under the MIT licence
  598. * Source: https://github.com/domchristie/to-markdown
  599. *
  600. * Code is altered:
  601. * - Added task list support: https://github.com/domchristie/to-markdown/pull/62
  602. * - He dependecy is removed
  603. */
  604. var toMarkdown = function(string) {
  605.  
  606. var ELEMENTS = [{
  607. patterns: 'p',
  608. replacement: function(str, attrs, innerHTML) {
  609. return innerHTML ? '\n\n' + innerHTML + '\n' : '';
  610. }
  611. }, {
  612. patterns: 'br',
  613. type: 'void',
  614. replacement: ' \n'
  615. }, {
  616. patterns: 'h([1-6])',
  617. replacement: function(str, hLevel, attrs, innerHTML) {
  618. var hPrefix = '';
  619. for (var i = 0; i < hLevel; i++) {
  620. hPrefix += '#';
  621. }
  622. return '\n\n' + hPrefix + ' ' + innerHTML + '\n';
  623. }
  624. }, {
  625. patterns: 'hr',
  626. type: 'void',
  627. replacement: '\n\n* * *\n'
  628. }, {
  629. patterns: 'a',
  630. replacement: function(str, attrs, innerHTML) {
  631. var href = attrs.match(attrRegExp('href')),
  632. title = attrs.match(attrRegExp('title'));
  633. return href ? '[' + innerHTML + ']' + '(' + href[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : str;
  634. }
  635. }, {
  636. patterns: ['b', 'strong'],
  637. replacement: function(str, attrs, innerHTML) {
  638. return innerHTML ? '**' + innerHTML + '**' : '';
  639. }
  640. }, {
  641. patterns: ['i', 'em'],
  642. replacement: function(str, attrs, innerHTML) {
  643. return innerHTML ? '_' + innerHTML + '_' : '';
  644. }
  645. }, {
  646. patterns: 'code',
  647. replacement: function(str, attrs, innerHTML) {
  648. return innerHTML ? '`' + innerHTML + '`' : '';
  649. }
  650. }, {
  651. patterns: 'img',
  652. type: 'void',
  653. replacement: function(str, attrs, innerHTML) {
  654. var src = attrs.match(attrRegExp('src')),
  655. alt = attrs.match(attrRegExp('alt')),
  656. title = attrs.match(attrRegExp('title'));
  657. return src ? '![' + (alt && alt[1] ? alt[1] : '') + ']' + '(' + src[1] + (title && title[1] ? ' "' + title[1] + '"' : '') + ')' : '';
  658. }
  659. }];
  660.  
  661. for (var i = 0, len = ELEMENTS.length; i < len; i++) {
  662. if (typeof ELEMENTS[i].patterns === 'string') {
  663. string = replaceEls(string, {
  664. tag: ELEMENTS[i].patterns,
  665. replacement: ELEMENTS[i].replacement,
  666. type: ELEMENTS[i].type
  667. });
  668. } else {
  669. for (var j = 0, pLen = ELEMENTS[i].patterns.length; j < pLen; j++) {
  670. string = replaceEls(string, {
  671. tag: ELEMENTS[i].patterns[j],
  672. replacement: ELEMENTS[i].replacement,
  673. type: ELEMENTS[i].type
  674. });
  675. }
  676. }
  677. }
  678.  
  679. function replaceEls(html, elProperties) {
  680. var pattern = elProperties.type === 'void' ? '<' + elProperties.tag + '\\b([^>]*)\\/?>' : '<' + elProperties.tag + '\\b([^>]*)>([\\s\\S]*?)<\\/' + elProperties.tag + '>',
  681. regex = new RegExp(pattern, 'gi'),
  682. markdown = '';
  683. if (typeof elProperties.replacement === 'string') {
  684. markdown = html.replace(regex, elProperties.replacement);
  685. } else {
  686. markdown = html.replace(regex, function(str, p1, p2, p3) {
  687. return elProperties.replacement.call(this, str, p1, p2, p3);
  688. });
  689. }
  690. return markdown;
  691. }
  692.  
  693. function attrRegExp(attr) {
  694. return new RegExp(attr + '\\s*=\\s*["\']?([^"\']*)["\']?', 'i');
  695. }
  696.  
  697. // Pre code blocks
  698.  
  699. string = string.replace(/<pre\b[^>]*>`([\s\S]*?)`<\/pre>/gi, function(str, innerHTML) {
  700. var text = innerHTML;
  701. text = text.replace(/^\t+/g, ' '); // convert tabs to spaces (you know it makes sense)
  702. text = text.replace(/\n/g, '\n ');
  703. return '\n\n ' + text + '\n';
  704. });
  705.  
  706. // Lists
  707.  
  708. // Escape numbers that could trigger an ol
  709. // If there are more than three spaces before the code, it would be in a pre tag
  710. // Make sure we are escaping the period not matching any character
  711. string = string.replace(/^(\s{0,3}\d+)\. /g, '$1\\. ');
  712.  
  713. // Converts lists that have no child lists (of same type) first, then works its way up
  714. var noChildrenRegex = /<(ul|ol)\b[^>]*>(?:(?!<ul|<ol)[\s\S])*?<\/\1>/gi;
  715. while (string.match(noChildrenRegex)) {
  716. string = string.replace(noChildrenRegex, function(str) {
  717. return replaceLists(str);
  718. });
  719. }
  720.  
  721. function replaceLists(html) {
  722.  
  723. html = html.replace(/<(ul|ol)\b[^>]*>([\s\S]*?)<\/\1>/gi, function(str, listType, innerHTML) {
  724. var lis = innerHTML.split('</li>');
  725. lis.splice(lis.length - 1, 1);
  726.  
  727. for (i = 0, len = lis.length; i < len; i++) {
  728. if (lis[i]) {
  729. var prefix = (listType === 'ol') ? (i + 1) + ". " : "* ";
  730. lis[i] = lis[i].replace(/\s*<li[^>]*>([\s\S]*)/i, function(str, innerHTML) {
  731. innerHTML = innerHTML.replace(/\s*<input[^>]*?(checked[^>]*)?type=['"]?checkbox['"]?[^>]>/, function(inputStr, checked) {
  732. return checked ? '[X]' : '[ ]';
  733. });
  734. innerHTML = innerHTML.replace(/^\s+/, '');
  735. innerHTML = innerHTML.replace(/\n\n/g, '\n\n ');
  736. // indent nested lists
  737. innerHTML = innerHTML.replace(/\n([ ]*)+(\*|\d+\.) /g, '\n$1 $2 ');
  738. return prefix + innerHTML;
  739. });
  740. }
  741. lis[i] = lis[i].replace(/(.) +$/m, '$1');
  742. }
  743. return lis.join('\n');
  744. });
  745.  
  746. return '\n\n' + html.replace(/[ \t]+\n|\s+$/g, '');
  747. }
  748.  
  749. // Blockquotes
  750. var deepest = /<blockquote\b[^>]*>((?:(?!<blockquote)[\s\S])*?)<\/blockquote>/gi;
  751. while (string.match(deepest)) {
  752. string = string.replace(deepest, function(str) {
  753. return replaceBlockquotes(str);
  754. });
  755. }
  756.  
  757. function replaceBlockquotes(html) {
  758. html = html.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, function(str, inner) {
  759. inner = inner.replace(/^\s+|\s+$/g, '');
  760. inner = cleanUp(inner);
  761. inner = inner.replace(/^/gm, '> ');
  762. inner = inner.replace(/^(>([ \t]{2,}>)+)/gm, '> >');
  763. return inner;
  764. });
  765. return html;
  766. }
  767.  
  768. function cleanUp(string) {
  769. string = string.replace(/^[\t\r\n]+|[\t\r\n]+$/g, ''); // trim leading/trailing whitespace
  770. string = string.replace(/\n\s+\n/g, '\n\n');
  771. string = string.replace(/\n{3,}/g, '\n\n'); // limit consecutive linebreaks to 2
  772. return string;
  773. }
  774.  
  775. return cleanUp(string);
  776. };
  777.  
  778. function getCommentTextarea(replyBtn) {
  779. var newComment = replyBtn;
  780. while (newComment && !newComment.classList.contains('js-quote-selection-container')) {
  781. newComment = newComment.parentNode;
  782. }
  783. if (newComment) {
  784. var lastElementChild = newComment.lastElementChild;
  785. lastElementChild.classList.add('open');
  786. newComment = lastElementChild.querySelector(".comment-form-textarea");
  787. } else {
  788. newComment = document.querySelector(".timeline-new-comment .comment-form-textarea");
  789. }
  790. return newComment;
  791. }
  792.  
  793. function addReplyButtons() {
  794. Array.prototype.forEach.call(document.querySelectorAll(".comment"), function(comment) {
  795. var oldReply = comment.querySelector(".GithubCommentEnhancerReply");
  796. if (oldReply) {
  797. oldReply.parentNode.removeChild(oldReply);
  798. }
  799.  
  800. var header = comment.querySelector(".timeline-comment-header"),
  801. actions = comment.querySelector(".timeline-comment-actions");
  802.  
  803. if (!header) {
  804. return;
  805. }
  806. if (!actions) {
  807. actions = document.createElement("div");
  808. actions.classList.add("timeline-comment-actions");
  809. header.insertBefore(actions, header.firstElementChild);
  810. }
  811.  
  812. var reply = document.createElement("a");
  813. reply.setAttribute("href", "#");
  814. reply.setAttribute("aria-label", "Reply to this comment");
  815. reply.classList.add("GithubCommentEnhancerReply", "timeline-comment-action", "tooltipped", "tooltipped-ne");
  816. reply.addEventListener("click", function(e) {
  817. e.preventDefault();
  818.  
  819. var newComment = getCommentTextarea(this);
  820.  
  821. var timestamp = comment.querySelector(".timestamp");
  822.  
  823. var commentText = comment.querySelector(".comment-form-textarea");
  824. if (commentText) {
  825. commentText = commentText.value;
  826. } else {
  827. commentText = toMarkdown(comment.querySelector(".comment-body").innerHTML);
  828. }
  829. commentText = commentText.trim().split("\n").map(function(line) {
  830. return "> " + line;
  831. }).join("\n");
  832.  
  833. var text = newComment.value.length > 0 ? "\n" : "";
  834. text += String.format('[**@{0}**]({1}/{0}) commented on [{2}]({3} "{4} - Replied by Github Comment Enhancer"):\n{5}\n\n',
  835. comment.querySelector(".author").textContent,
  836. location.origin,
  837. timestamp.firstElementChild.getAttribute("title"),
  838. timestamp.href,
  839. timestamp.firstElementChild.getAttribute("datetime"),
  840. commentText);
  841.  
  842. newComment.value += text;
  843. newComment.setSelectionRange(newComment.value.length, newComment.value.length);
  844. newComment.focus();
  845. });
  846.  
  847. var replyIcon = document.createElement("span");
  848. replyIcon.classList.add("octicon", "octicon-mail-reply");
  849. reply.appendChild(replyIcon);
  850.  
  851. actions.appendChild(reply);
  852. });
  853. }
  854.  
  855. // init;
  856. function init() {
  857. addToolbar();
  858. addReplyButtons();
  859. }
  860. init();
  861.  
  862. // on pjax;
  863. unsafeWindow.$(document).on("pjax:end", init); // `pjax:end` also runs on history back;
  864.  
  865. // For inline comments on commits;
  866. var files = document.querySelectorAll('.diff-table');
  867. Array.prototype.forEach.call(files, function(file) {
  868. file = file.firstElementChild;
  869. new MutationObserver(function(mutations) {
  870. mutations.forEach(function(mutation) {
  871. if (mutation.target === file) {
  872. addToolbar();
  873. }
  874. });
  875. }).observe(file, {
  876. childList: true,
  877. subtree: true
  878. });
  879. });
  880.  
  881. })(typeof unsafeWindow !== "undefined" ? unsafeWindow : window);

QingJ © 2025

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