Github Comment Enhancer

Enhances Github comments

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

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

QingJ © 2025

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