Medium: Editor For Programmers

Use `code` for inline code. Automatically fix quotes in code tags. Link to a section of text easily.

目前为 2016-07-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Medium: Editor For Programmers
  3. // @namespace https://github.com/Zren/
  4. // @version 3
  5. // @description Use `code` for inline code. Automatically fix quotes in code tags. Link to a section of text easily.
  6. // @author Zren
  7. // @icon https://medium.com/favicon.ico
  8. // @match https://medium.com/p/*/edit
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function(){
  13. function wrapInlineCode() {
  14. var tagBefore = '<code class="markup--code"><strong class="markup--strong">';
  15. var tagAfter = '</strong></code>';
  16.  
  17. var sel = window.getSelection();
  18. if (sel.type === 'Caret' && sel.focusNode.nodeName === "#text") {
  19. // Note: Does not trigger if first character in text node, since the focus isn't a Text node.
  20. var str = sel.focusNode.nodeValue;
  21. var before = str.substr(0, sel.focusOffset - 1);
  22. var after = str.substr(sel.focusOffset);
  23. console.log("Before: ", before);
  24. console.log("After: ", after);
  25. var index = before.lastIndexOf('`');
  26. if (index >= 0) {
  27. // we just typed the right quote
  28. if (index === before.length-1) {
  29. // ``
  30. // Ignore
  31. } else {
  32. // `...`
  33. var range = document.createRange();
  34. range.setStart(sel.focusNode, index);
  35. range.setEnd(sel.focusNode, sel.focusOffset);
  36. sel.removeAllRanges();
  37. sel.addRange(range);
  38. var html = range.toString();
  39. html = html.substr(1, html.length-2); // trim ``
  40. html = tagBefore + html + tagAfter + ' ';
  41. document.execCommand('insertHTML', false, html);
  42. range.collapse(false); // move cursor to end
  43. return;
  44. }
  45. }
  46. var index = after.indexOf('`');
  47. if (index >= 0) {
  48. // we just typed the left quote
  49. if (index == 0) {
  50. // ``
  51. // Ignore
  52. } else {
  53. var range = document.createRange();
  54. range.setStart(sel.focusNode, sel.focusOffset - 1);
  55. range.setEnd(sel.focusNode, sel.focusOffset + index + 1);
  56. sel.removeAllRanges();
  57. sel.addRange(range);
  58. var html = range.toString();
  59. html = html.substr(1, html.length-2); // trim ``
  60. html = tagBefore + html + tagAfter + ' ';
  61. document.execCommand('insertHTML', false, html);
  62. range.collapse(false); // move cursor to end
  63. return;
  64. }
  65. }
  66. }
  67. }
  68. function alwaysBrInPre(e) {
  69. var sel = window.getSelection();
  70. if (sel.type === 'Caret' && sel.focusNode.nodeName === "#text") {
  71. console.log(sel.focusNode.nodeValue.length, sel.focusOffset, sel.focusNode.nodeValue.substr(sel.focusOffset), sel.focusNode);
  72. if (sel.focusOffset !== 0) return; // End of line = selecting start of next line.
  73.  
  74. // Focused on start of line.
  75. var el = sel.focusNode;
  76. while (el) {
  77. if (!el.parentNode) break; // Don't run on #document element since it doesn't have el.hasAttribute
  78. if (el.classList && el.classList.contains('section-inner')) break; // Ignore everything outside the post itself.
  79. if (el != el.parentNode.firstChild) break; // Only match end of line = start of next line.
  80. if (el.parentNode.tagName == 'PRE') {
  81. // Insert linebreak <br>
  82. var secondPre = el.parentNode;
  83. var firstPre = secondPre.previousSibling;
  84. firstPre.appendChild(document.createElement('br')); // <br> removed during split
  85. var newFocusLine = document.createElement('br');
  86. firstPre.appendChild(newFocusLine); // The actual <br> we wanted to enter.
  87. // Move all elements back into the first pre.
  88. while (secondPre.firstChild) {
  89. firstPre.appendChild(secondPre.firstChild);
  90. }
  91. // Delete the second <pre>
  92. // We can't remove it since it will break the entire editor...
  93. secondPre.appendChild(document.createElement('br'));
  94. //secondPre.remove();
  95. //document.execCommand('delete');
  96. // Move cursor to new line.
  97. var range = document.createRange();
  98. range.setStart(newFocusLine, 0);
  99. range.collapse(true);
  100. sel.removeAllRanges();
  101. sel.addRange(range);
  102. e.preventDefault();
  103. }
  104. el = el.parentNode;
  105. }
  106. }
  107. }
  108.  
  109. function onKeyDown(e) {
  110. if (e.key === '`') {
  111. setTimeout(wrapInlineCode, 100); // Wait for ` to be written so we can replace it
  112. } else if (e.keyCode == 9) { // Tab
  113. e.preventDefault();
  114. } else if (e.keyCode == 13) { // Enter
  115. alwaysBrInPre(e);
  116. } else if (e.key == '6' && e.ctrlKey && e.altKey) {
  117. console.log('CTRL+ALT+6', e);
  118. } else {
  119. console.log('Key:', e.key, e.ctrlKey, e.altKey);
  120. }
  121. }
  122. function fixQuotes() {
  123. // Fix quotes in <pre> and <code> tags.
  124. setInterval(function(){
  125. var codeTags = document.querySelectorAll('pre, code');
  126. for (var tag of codeTags) {
  127. if (tag.innerHTML.indexOf('“') >= 0 || tag.innerHTML.indexOf('”') >= 0) {
  128. tag.innerHTML = tag.innerHTML.replace('“', '"').replace('”', '"');
  129. }
  130. }
  131. }, 1000);
  132. }
  133. function showPermalink() {
  134. // Setup (temporary) permalink to line.
  135. var tag = document.createElement('a');
  136. tag.style.position = 'absolute';
  137. tag.style.display = 'block';
  138. tag.style.top = '-9999px';
  139. tag.style.left = 0;
  140. tag.style.color = '#888';
  141. tag.innerHTML = '¶'; //'[link]';
  142. document.body.appendChild(tag);
  143.  
  144. function onMouseOver(e) {
  145. var el = e.relatedTarget || e.target;
  146. while (el) {
  147. if (!el.parentNode) break; // Don't run on #document element since it doesn't have el.hasAttribute
  148. if (el.classList.contains('section-inner')) break; // Ignore everything outside the post itself.
  149. if (el.hasAttribute('name')) {
  150. showTag(el)
  151. break;
  152. }
  153. el = el.parentNode;
  154. }
  155. }
  156. function showTag(el) {
  157. var rect = el.getBoundingClientRect();
  158. var url = document.querySelector('link[rel="canonical"]').href;
  159. url = url.substr(0, url.length - '/edit'.length);
  160. url += '#' + el.getAttribute('name');
  161. tag.href = 'javascript:window.history.replaceState({}, "", "' + url + '")';
  162. var tagGuide = document.querySelector('.section-inner');
  163. var tagGuideRect = tagGuide.getBoundingClientRect();
  164. var tagRect = tag.getBoundingClientRect();
  165. tag.style.left = '' + (tagGuideRect.left + window.scrollX - tagRect.width - 60) + 'px';
  166. tag.style.top = '' + (rect.top + window.scrollY) + 'px';
  167. }
  168.  
  169. var taggedElements = document.querySelectorAll('.postArticle-content');
  170. for (var el of taggedElements) {
  171. //el.addEventListener('mouseover', onMouseOver, true);
  172. el.addEventListener('click', onMouseOver, true);
  173. }
  174. }
  175. function onLoad() {
  176. // Bind keys
  177. var main = document.querySelector('main[contenteditable="true"]');
  178. main.addEventListener('keydown', onKeyDown, true);
  179. fixQuotes();
  180. showPermalink();
  181. }
  182.  
  183. function waitForLoad() {
  184. var main = document.querySelector('main[contenteditable="true"]');
  185. if (main) {
  186. onLoad();
  187. console.log('[Medium: Markdown] Loaded');
  188. } else {
  189. setTimeout(waitForLoad, 100);
  190. }
  191. }
  192. setTimeout(waitForLoad, 100);
  193. })();
  194.