Easy Markdown for StackExchange

Adds "Markdown" and "Copy" buttons to display original Markdown content in Stackoverflow

当前为 2023-11-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Easy Markdown for StackExchange
  3. // @version 2.1
  4. // @author Murphy Lo (http://github.com/MurphyLo)
  5. // @description Adds "Markdown" and "Copy" buttons to display original Markdown content in Stackoverflow
  6. // @license GNU GPL v3 (http://www.gnu.org/copyleft/gpl.html)
  7. // @match https://*.stackoverflow.com/questions/*
  8. // @match https://*.superuser.com/questions/*
  9. // @match https://*.serverfault.com/questions/*
  10. // @match https://*.stackexchange.com/questions/*
  11. // @match https://*.askubuntu.com/questions/*
  12. // @match https://*.math.stackexchange.com/questions/*
  13. // @match https://*.tex.stackexchange.com/questions/*
  14. // @match https://*.english.stackexchange.com/questions/*
  15. // @match https://*.gaming.stackexchange.com/questions/*
  16. // @match https://*.physics.stackexchange.com/questions/*
  17. // @match https://*.chemistry.stackexchange.com/questions/*
  18. // @match https://*.biology.stackexchange.com/questions/*
  19. // @match https://*.programmers.stackexchange.com/questions/*
  20. // @match https://*.electronics.stackexchange.com/questions/*
  21. // @grant none
  22. // @namespace https://greasyfork.org/users/1224148
  23. // ==/UserScript==
  24.  
  25. // This script is a modified version of an original script by Manish Goregaokar (http://stackapps.com/users/10098/manishearth)
  26. // Original script: https://github.com/Manishearth/Manish-Codes/raw/master/StackExchange/PrintPost.user.js
  27. // The original script is licensed under the GNU GPL v3 (http://www.gnu.org/copyleft/gpl.html)
  28. // Modifications made by Murphy Lo
  29.  
  30. (async function() {
  31. 'use strict';
  32.  
  33. // Function to fetch content of a post
  34. async function fetchMarkdown(postId) {
  35. const response = await fetch(`/posts/${postId}/edit-inline`);
  36. const data = await response.text();
  37. let markdown = data.match(/<textarea[^>]*>([\s\S]*?)<\/textarea>/)[1];
  38.  
  39. // Decode HTML entities
  40. return decodeHtmlEntities(markdown);
  41. }
  42.  
  43. // Function to decode HTML entities from the content into Markdown plain text
  44. function decodeHtmlEntities(str) {
  45. const tempElement = document.createElement('div');
  46. tempElement.innerHTML = str;
  47. return tempElement.textContent;
  48. }
  49.  
  50. // Function to show markdown content in a modal
  51. function showMarkdown(event) {
  52. // Prevent the default action
  53. event.preventDefault();
  54.  
  55. // Disable scrolling on the main page
  56. const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
  57. document.body.style.overflow = 'hidden';
  58. document.body.style.paddingRight = `${scrollbarWidth}px`;
  59.  
  60. // Add paddingRight to fixed positioned elements
  61. const fixedElements = document.querySelectorAll('header.s-topbar');
  62. fixedElements.forEach(el => el.style.paddingRight = `${scrollbarWidth}px`);
  63.  
  64. // Create a modal to display the markdown (updated style)
  65. const modal = document.createElement('div');
  66. modal.style.cssText = `
  67. position: fixed;
  68. z-index: 5051;
  69. left: 0;
  70. top: 0;
  71. width: 100%;
  72. height: 100%;
  73. overflow: none;
  74. background-color: rgba(0,0,0,0.5);`;
  75.  
  76. const modalContent = document.createElement('div');
  77. modalContent.style.cssText = `
  78. background-color: #FFF;
  79. margin: 5% auto;
  80. padding: 20px;
  81. border: 1px solid #888;
  82. width: 70%;
  83. max-height: 90%;
  84. overflow-y: auto;
  85. box-sizing: border-box;
  86. border-radius: 5px;
  87. box-shadow: 0 4px 8px 0 rgba(0,0,0,0.3);`;
  88.  
  89. // Updated close button style
  90. const closeButton = document.createElement('span');
  91. closeButton.style.cssText = `
  92. color: #aaa;
  93. float: right;
  94. font-size: 28px;
  95. font-weight: bold;
  96. cursor: pointer;
  97. margin-left: 10px;`;
  98. closeButton.textContent = '✕';
  99. closeButton.onmouseover = () => closeButton.style.color = 'black';
  100. closeButton.onmouseout = () => closeButton.style.color = '#aaa';
  101.  
  102. const markdownElement = document.createElement('pre');
  103. markdownElement.style.cssText = `
  104. margin: 0;
  105. white-space: pre-wrap;
  106. word-wrap: break-word;
  107. overflow-y: auto;`;
  108. markdownElement.textContent = this.markdownContent;
  109.  
  110. modalContent.appendChild(closeButton);
  111. modalContent.appendChild(markdownElement);
  112. modal.appendChild(modalContent);
  113. document.body.appendChild(modal);
  114.  
  115. // Close modal behaviors
  116. function closeModal() {
  117. // Removing the modal from the DOM
  118. document.body.removeChild(modal);
  119. document.removeEventListener('keydown', handleKeyDown);
  120.  
  121. // Re-enable scrolling on the main page and remove the paddingRight
  122. document.body.style.overflow = '';
  123. document.body.style.paddingRight = '';
  124.  
  125. // Remove paddingRight from fixed positioned elements
  126. const fixedElements = document.querySelectorAll('header.s-topbar');
  127. fixedElements.forEach(el => el.style.paddingRight = '');
  128. }
  129.  
  130. // Click `x` to close modal
  131. closeButton.onclick = closeModal;
  132. // Press `Esc` to close modal
  133. const handleKeyDown = (event) => event.keyCode === 27 && closeModal(); // 27 is the keyCode for the Esc key
  134. document.addEventListener('keydown', handleKeyDown);
  135. // Click modal background to close modal
  136. modal.onclick = (e) => e.target === modal && closeModal();
  137.  
  138. }
  139.  
  140. // Function to copy text to clipboard
  141. async function copyToClipboard(text) {
  142. try {
  143. await navigator.clipboard.writeText(text);
  144. // console.log('Markdown content copied to clipboard');
  145. } catch (err) {
  146. console.error('Failed to copy Markdown content: ', err);
  147. }
  148. }
  149.  
  150. // Add "Markdown" and "Copy" buttons to each post
  151. const posts = document.querySelectorAll('.question, .answer');
  152. for (const post of posts) {
  153. const postMenu = post.querySelector('.d-flex.gs8.s-anchors.s-anchors__muted.fw-wrap');
  154. const separator = document.createElement('span');
  155. separator.className = 'lsep';
  156. separator.textContent = '|';
  157. postMenu.appendChild(separator);
  158.  
  159. // Add Markdown button
  160. const printButton = document.createElement('a');
  161. printButton.href = '#';
  162. printButton.textContent = 'Markdown';
  163. printButton.title = 'View this post\'s original Markdown content';
  164. printButton.onclick = showMarkdown;
  165.  
  166. const printButtonWrapper = document.createElement('div');
  167. printButtonWrapper.className = 'flex--item';
  168. printButtonWrapper.appendChild(printButton);
  169.  
  170. postMenu.appendChild(printButtonWrapper);
  171.  
  172. // Add Copy button
  173. const copyButton = document.createElement('a');
  174. copyButton.href = '#';
  175. copyButton.textContent = 'Copy';
  176. copyButton.title = 'Copy this post\'s original Markdown content to clipboard';
  177. copyButton.onclick = (event) => {
  178. event.preventDefault();
  179. copyToClipboard(printButton.markdownContent);
  180. };
  181.  
  182. const copyButtonWrapper = document.createElement('div');
  183. copyButtonWrapper.className = 'flex--item';
  184. copyButtonWrapper.appendChild(copyButton);
  185.  
  186. postMenu.appendChild(copyButtonWrapper);
  187.  
  188. // Fetch and store markdown content
  189. const postId = post.dataset.questionid || post.dataset.answerid;
  190. printButton.markdownContent = await fetchMarkdown(postId);
  191. }
  192. })();