Github Comment Enhancer

Enhances Github comments

目前为 2015-03-08 提交的版本。查看 最新版本

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

QingJ © 2025

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