Medium: Editor For Programmers

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

Tính đến 09-07-2016. Xem phiên bản mới nhất.

  1. // ==UserScript==
  2. // @name Medium: Editor For Programmers
  3. // @namespace https://github.com/Zren/
  4. // @version 4
  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. firstPre.appendChild(document.createTextNode(''));
  86. var newFocusLine = document.createElement('br');
  87. firstPre.appendChild(newFocusLine); // The actual <br> we wanted to enter.
  88. // Move all elements back into the first pre.
  89. while (secondPre.firstChild) {
  90. firstPre.appendChild(secondPre.firstChild);
  91. }
  92. // Delete the second <pre>
  93. // We can't remove it since it will break the entire editor...
  94. secondPre.appendChild(document.createElement('br'));
  95. //secondPre.remove();
  96. //document.execCommand('delete');
  97. // Move cursor to new line.
  98. var range = document.createRange();
  99. range.setStart(newFocusLine, 0);
  100. range.collapse(true);
  101. sel.removeAllRanges();
  102. sel.addRange(range);
  103. e.preventDefault();
  104. }
  105. el = el.parentNode;
  106. }
  107. }
  108. }
  109.  
  110. function onKeyDown(e) {
  111. if (e.key === '`') {
  112. setTimeout(wrapInlineCode, 100); // Wait for ` to be written so we can replace it
  113. } else if (e.keyCode == 9) { // Tab
  114. e.preventDefault();
  115. } else if (e.keyCode == 13) { // Enter
  116. alwaysBrInPre(e);
  117. } else if (e.key == '6' && e.ctrlKey && e.altKey) {
  118. console.log('CTRL+ALT+6', e);
  119. } else {
  120. console.log('Key:', e.key, e.ctrlKey, e.altKey);
  121. }
  122. }
  123. function fixQuotes() {
  124. // Fix quotes in <pre> and <code> tags.
  125. setInterval(function(){
  126. var codeTags = document.querySelectorAll('pre, code');
  127. for (var tag of codeTags) {
  128. if (tag.innerHTML.indexOf('“') >= 0 || tag.innerHTML.indexOf('”') >= 0) {
  129. tag.innerHTML = tag.innerHTML.replace('“', '"').replace('”', '"');
  130. }
  131. }
  132. }, 1000);
  133. }
  134. function showPermalink() {
  135. // Setup (temporary) permalink to line.
  136. var tag = document.createElement('a');
  137. tag.style.position = 'absolute';
  138. tag.style.display = 'block';
  139. tag.style.top = '-9999px';
  140. tag.style.left = 0;
  141. tag.style.color = '#888';
  142. tag.innerHTML = '¶'; //'[link]';
  143. document.body.appendChild(tag);
  144.  
  145. function onMouseOver(e) {
  146. var el = e.relatedTarget || e.target;
  147. while (el) {
  148. if (!el.parentNode) break; // Don't run on #document element since it doesn't have el.hasAttribute
  149. if (el.classList.contains('section-inner')) break; // Ignore everything outside the post itself.
  150. if (el.hasAttribute('name')) {
  151. showTag(el)
  152. break;
  153. }
  154. el = el.parentNode;
  155. }
  156. }
  157. function showTag(el) {
  158. var rect = el.getBoundingClientRect();
  159. var url = document.querySelector('link[rel="canonical"]').href;
  160. url = url.substr(0, url.length - '/edit'.length);
  161. url += '#' + el.getAttribute('name');
  162. tag.href = 'javascript:window.history.replaceState({}, "", "' + url + '")';
  163. var tagGuide = document.querySelector('.section-inner');
  164. var tagGuideRect = tagGuide.getBoundingClientRect();
  165. var tagRect = tag.getBoundingClientRect();
  166. tag.style.left = '' + (tagGuideRect.left + window.scrollX - tagRect.width - 60) + 'px';
  167. tag.style.top = '' + (rect.top + window.scrollY) + 'px';
  168. }
  169.  
  170. var taggedElements = document.querySelectorAll('.postArticle-content');
  171. for (var el of taggedElements) {
  172. //el.addEventListener('mouseover', onMouseOver, true);
  173. el.addEventListener('click', onMouseOver, true);
  174. }
  175. }
  176. function onLoad() {
  177. // Bind keys
  178. var main = document.querySelector('main[contenteditable="true"]');
  179. main.addEventListener('keydown', onKeyDown, true);
  180. fixQuotes();
  181. showPermalink();
  182. }
  183.  
  184. function waitForLoad() {
  185. var main = document.querySelector('main[contenteditable="true"]');
  186. if (main) {
  187. onLoad();
  188. console.log('[Medium: Markdown] Loaded');
  189. } else {
  190. setTimeout(waitForLoad, 100);
  191. }
  192. }
  193. setTimeout(waitForLoad, 100);
  194. })();
  195.