Comment Zoomer

Add zoom button in github comment to provide full screen mode, allowing you to write comments more elegantly

  1. // ==UserScript==
  2. // @name Comment Zoomer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.1
  5. // @description Add zoom button in github comment to provide full screen mode, allowing you to write comments more elegantly
  6. // @author IsaacKam
  7. // @match https://github.com/*
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const namespace = '__ISSUE_EDITOR__';
  17. const emToggleState = {
  18. IDLE: '',
  19. NORMAL: 'normal',
  20. FULL_SCREEN: 'full_screen'
  21. };
  22. const emPreviewState = {
  23. IDLE: '',
  24. NORMAL: 'normal',
  25. PREVIEW: 'preview'
  26. };
  27. const store = {
  28. sup: null,
  29. previewTimer: 0,
  30. toggleStatus: emToggleState.IDLE,
  31. previewStatus: emPreviewState.IDLE
  32. };
  33.  
  34. const initStyle = () => {
  35. const style = document.createElement('style');
  36. const cssText = document.createTextNode(`
  37. .issue-editor__body--disable-scroll {
  38. overflow: hidden;
  39. }
  40. .issue-editor__entry-btn {
  41. display: inline-block;
  42. cursor: pointer;
  43. user-select: none;
  44. color: rgb(87, 96, 106);
  45. margin-left: 8px;
  46. vertical-align: top;
  47. }
  48. .issue-editor__caret {
  49. position: fixed;
  50. top: 0;
  51. right: 0;
  52. bottom: 0;
  53. left: 0;
  54. z-index: 1001;
  55. }
  56. .issue-editor__textarea {
  57. max-height: none !important;
  58. height: calc(100vh - 200px) !important;
  59. }
  60. .issue-editor__file-attachment {
  61. margin-left: 15px;
  62. }
  63. .issue-editor__file-attachment--preview {
  64. width: 50vw;
  65. }
  66. .issue-editor__file-attachment-write-content--preview {
  67. display: block !important;
  68. }
  69. .issue-editor__preview-content--preview {
  70. width: 50vw;
  71. margin-left: calc(50vw + 8px) !important;
  72. display: block !important;
  73. position: absolute;
  74. top: 56px;
  75. height: calc(100vh - 170px) !important;
  76. overflow: auto;
  77. border: 1px solid darkturquoise;
  78. border-radius: 2px;
  79. }
  80. markdown-toolbar.issue-editor__markdown-toolbar {
  81. display: block !important;
  82. }
  83. `);
  84. style.appendChild(cssText);
  85. document.head.appendChild(style);
  86. };
  87.  
  88. const nodes = {};
  89. const intiGetter = (get) => ({ get });
  90. Object.defineProperties(nodes, {
  91. discussionBucket: intiGetter(
  92. () => document.querySelector("#discussion_bucket")),
  93. caret: intiGetter(
  94. () => store.sup.querySelector(".timeline-comment--caret")),
  95. markdownToolbar: intiGetter(
  96. () => store.sup.querySelector("markdown-toolbar")),
  97. textarea: intiGetter(
  98. () => store.sup.querySelector("textarea.js-comment-field")),
  99. commentFormActions: intiGetter(
  100. () => store.sup.querySelector('.comment-form-actions')),
  101. newCommentSup: intiGetter(
  102. () => nodes.discussionBucket.querySelector('.discussion-timeline-actions')),
  103. newCommentFormActions: intiGetter(
  104. () => nodes.newCommentSup.querySelector('#partial-new-comment-form-actions')),
  105. fileAttachment: intiGetter(
  106. () => store.sup.querySelector('file-attachment')),
  107. fileAttachmentWriteContent: intiGetter(
  108. () => nodes.fileAttachment.querySelector('.write-content')),
  109. previewContent: intiGetter(
  110. () => store.sup.querySelector('.preview-content')),
  111. previewEntery: intiGetter(
  112. () => store.sup.querySelector('.issue-editor__preview-btn')),
  113. updateEntery: intiGetter(
  114. () => store.sup.querySelector('.issue-editor__update-btn')),
  115. previewTab: intiGetter(
  116. () => store.sup.querySelector('.preview-tab')),
  117. writeTab: intiGetter(
  118. () => store.sup.querySelector('.write-tab')),
  119. });
  120.  
  121. window[namespace] = {};
  122. window[namespace].store = store;
  123. window[namespace].nodes = nodes;
  124.  
  125. function addClass({ node, classname }) {
  126. const classSet = new Set(node.classList);
  127. classSet.add(classname);
  128. node.setAttribute('class', Array.from(classSet).join(' '));
  129. }
  130.  
  131. function removeClass({ node, classname }) {
  132. const classSet = new Set(node.classList);
  133. classSet.delete(classname);
  134. node.setAttribute('class', Array.from(classSet).join(' '));
  135. }
  136.  
  137. function fullScreen() {
  138. store.toggleStatus = emToggleState.FULL_SCREEN;
  139. addClass({ node: document.body, classname: 'issue-editor__body--disable-scroll' });
  140. addClass({ node: nodes.caret, classname: 'issue-editor__caret' });
  141. addClass({ node: nodes.textarea, classname: 'issue-editor__textarea' });
  142. addClass({ node: nodes.fileAttachment, classname: 'issue-editor__file-attachment' });
  143.  
  144. appendPreviewEntery();
  145.  
  146. Object.assign(nodes.markdownToolbar.style, {
  147. display: 'block !important'
  148. });
  149.  
  150. nodes.textarea.focus();
  151. }
  152.  
  153. function normalScreen() {
  154. store.toggleStatus = emToggleState.NORMAL;
  155. removeClass({ node: document.body, classname: 'issue-editor__body--disable-scroll' });
  156. removeClass({ node: nodes.caret, classname: 'issue-editor__caret' });
  157. removeClass({ node: nodes.textarea, classname: 'issue-editor__textarea' });
  158. removeClass({ node: nodes.fileAttachment, classname: 'issue-editor__file-attachment' });
  159. removePreviewEntry();
  160. }
  161.  
  162. function setPreviewMode() {
  163. store.previewStatus = emPreviewState.PREVIEW;
  164. addClass({
  165. node: nodes.fileAttachment,
  166. classname: 'issue-editor__file-attachment--preview'
  167. });
  168. addClass({
  169. node: nodes.fileAttachmentWriteContent,
  170. classname: 'issue-editor__file-attachment-write-content--preview'
  171. });
  172. addClass({
  173. node: nodes.previewContent,
  174. classname: 'issue-editor__preview-content--preview'
  175. });
  176. addClass({
  177. node: nodes.markdownToolbar,
  178. classname: 'issue-editor__markdown-toolbar'
  179. });
  180. nodes.previewTab.click();
  181. appendEntry({
  182. text: 'Update',
  183. classname: 'issue-editor__update-btn',
  184. onclick() {
  185. nodes.previewTab.click();
  186. }
  187. });
  188. Object.assign(nodes.markdownToolbar.style, {
  189. display: 'block !important'
  190. });
  191. nodes.markdownToolbar.setAttribute('style', 'display: block !important');
  192. }
  193.  
  194. function removePreviewMode() {
  195. store.previewStatus = emPreviewState.NORMAL;
  196. removeClass({
  197. node: nodes.fileAttachment,
  198. classname: 'issue-editor__file-attachment--preview'
  199. });
  200. removeClass({
  201. node: nodes.fileAttachmentWriteContent,
  202. classname: 'issue-editor__file-attachment-write-content--preview'
  203. });
  204. removeClass({
  205. node: nodes.previewContent,
  206. classname: 'issue-editor__preview-content--preview'
  207. });
  208. removeClass({
  209. node: nodes.markdownToolbar,
  210. classname: 'issue-editor__markdown-toolbar'
  211. });
  212. removeEntry(nodes.updateEntery);
  213. nodes.writeTab.click();
  214. nodes.writeTab.focus();
  215. clearTimeout(store.previewTimer);
  216. }
  217.  
  218. function interceptCommentFormActions(node) {
  219. node.onclick = () => {
  220. if (store.toggleStatus !== emToggleState.FULL_SCREEN) {
  221. return;
  222. }
  223. normalScreen();
  224. };
  225. interceptSubmitComment(node);
  226. }
  227. function interceptSubmitComment(node) {
  228. const submit = node.querySelector('[type=submit]');
  229. const oldSubmitClick = submit.click;
  230. submit.onclick = () => {
  231. oldSubmitClick();
  232. setTimeout(() => {
  233. location.reload();
  234. }, 500);
  235. }
  236. }
  237. function appendEntry({
  238. text,
  239. classname,
  240. onclick
  241. }) {
  242. const isExist = nodes.markdownToolbar.querySelector('.' + classname);
  243. if (isExist) {
  244. return;
  245. }
  246.  
  247. const node = document.createElement('div');
  248. const classnameSet = new Set(['issue-editor__entry-btn', classname]);
  249. addClass({ node, classname: Array.from(classnameSet).join(' ') })
  250. node.innerHTML = text;
  251. if (onclick) {
  252. node.onclick = onclick;
  253. }
  254. nodes.markdownToolbar.append(node);
  255. return node;
  256. }
  257.  
  258. function appendZoomEntry(sup) {
  259. const zoom = appendEntry({
  260. text: 'Zoom',
  261. classname: 'issue-editor__zoom-btn',
  262. onclick() {
  263. console.info('[node.onclick] invoked!');
  264. if (store.toggleStatus === emToggleState.FULL_SCREEN) {
  265. normalScreen();
  266. return;
  267. }
  268. store.sup =sup;
  269. fullScreen();
  270. }
  271. });
  272. return zoom;
  273. }
  274.  
  275. function appendPreviewEntery() {
  276. appendEntry({
  277. text: 'Preview',
  278. classname: 'issue-editor__preview-btn',
  279. onclick() {
  280. if (store.previewStatus === emPreviewState.PREVIEW) {
  281. removePreviewMode();
  282. return;
  283. }
  284.  
  285. setPreviewMode();
  286. }
  287. });
  288. }
  289.  
  290. function removeEntry(node) {
  291. if (!node) {
  292. return;
  293. }
  294. node.onclick = null;
  295. node.parentElement.removeChild(node);
  296. }
  297.  
  298. function removePreviewEntry() {
  299. removeEntry(nodes.previewEntery)
  300. removePreviewMode();
  301. clearTimeout(store.previewTimer);
  302. }
  303.  
  304. function initHistoryCommentEntries() {
  305. const parent = nodes.discussionBucket.querySelector(".js-discussion.js-socket-channel");
  306. const findEditBtn = (node) => node.querySelector('.js-comment-edit-button');
  307. const findAllTimelineItem = (node) => Array.from(node.querySelectorAll('.js-timeline-item'));
  308. const loopFindEditBtn = (it, cb) => {
  309. const editBtn = findEditBtn(it);
  310. if (editBtn) {
  311. cb(editBtn);
  312. return;
  313. }
  314. setTimeout(() => {
  315. loopFindEditBtn(it, cb)
  316. }, 200);
  317. };
  318. const findDetailBtn = (node) => node.querySelectorAll('details')[1];
  319. const getItem = (sup) => ({ sup, sub: findDetailBtn(sup) });
  320. const main = parent.children[0];
  321. const rest = parent.children[1];
  322.  
  323. const items = [
  324. getItem(main),
  325. ...findAllTimelineItem(rest).map(it => getItem(it))
  326. ].filter(it => it.sub);
  327.  
  328. for (const it of items) {
  329. it.sub.onclick = () => {
  330. it.sub.onclick = null;
  331. loopFindEditBtn(it.sub, (editBtn) => {
  332. editBtn.onclick = () => {
  333. store.sup = it.sup;
  334. appendZoomEntry(it.sup);
  335. interceptCommentFormActions(nodes.commentFormActions);
  336. }
  337. });
  338. };
  339. }
  340. }
  341.  
  342. function initNewCommentEntry() {
  343. store.sup = nodes.newCommentSup;
  344. appendZoomEntry(nodes.newCommentSup);
  345. interceptCommentFormActions(nodes.newCommentFormActions);
  346. }
  347.  
  348. function ready(cb) {
  349. if (nodes.discussionBucket) {
  350. return setTimeout(cb, 200);
  351. }
  352. setTimeout(() => ready(cb), 200)
  353. }
  354.  
  355. ready(() => {
  356. console.info('invoked Issue Editor!');
  357. initStyle();
  358. initNewCommentEntry();
  359. initHistoryCommentEntries();
  360. });
  361. })();

QingJ © 2025

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