Syntaxify

Universal syntax highlighting

  1. // ==UserScript==
  2. // @name Syntaxify
  3. // @description Universal syntax highlighting
  4. // @namespace http://rob-bolton.co.uk
  5. // @version 1.4
  6. // @include http*
  7. // @grant GM_addStyle
  8. // @grant GM_getResourceText
  9. // @require http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/highlight.min.js
  10. // @resource highlightJsCss https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/monokai.min.css
  11. // ==/UserScript==
  12. //
  13.  
  14. var tagsToSearch = ["pre", "code"];
  15.  
  16. GM_addStyle(GM_getResourceText("highlightJsCss"));
  17.  
  18. var codeBlocks = {};
  19. var itemCounter = 0;
  20. var selectedItem;
  21.  
  22. function isCodeBlock(node) {
  23. if(node) {
  24. var tagname = node.tagName.toLowerCase();
  25. for(var i=0; i<tagsToSearch.length; i++) {
  26. if(tagname == tagsToSearch[i]) {
  27. return true;
  28. }
  29. }
  30. }
  31. return false;
  32. }
  33.  
  34. function wrapSelection() {
  35. var selection = window.getSelection();
  36. if(selection.rangeCount > 0) {
  37. var range = selection.getRangeAt(0);
  38. var parent = range.startContainer.parentNode;
  39. if(!isCodeBlock(parent)) {
  40. var container = document.createElement("code");
  41. var containerData = addBlock(container);
  42. containerData.originalFragment = range.cloneContents();
  43. container.setAttribute("contextmenu", "SyntaxifyPreUnwrapMenu");
  44. range.surroundContents(container);
  45. }
  46. }
  47. }
  48.  
  49. function unwrap() {
  50. if(selectedItem) {
  51. var node = selectedItem;
  52. var nodeData = codeBlocks[node.id];
  53. var parent = node.parentNode;
  54. parent.insertBefore(nodeData.originalFragment, node);
  55. parent.removeChild(node);
  56. }
  57. }
  58.  
  59. function addBlock(node) {
  60. if(!node.id) {
  61. node.id = "SyntaxifyBlock" + itemCounter++;
  62. }
  63. codeBlocks[node.id] = {
  64. "node": node,
  65. "highlighted": false,
  66. "originalNode": node.cloneNode(true)
  67. }
  68. return codeBlocks[node.id];
  69. }
  70.  
  71. function revert(nodeData) {
  72. var parent = nodeData.node.parentNode;
  73. parent.insertBefore(nodeData.originalNode, nodeData.node);
  74. parent.removeChild(nodeData.node);
  75. nodeData.node = nodeData.originalNode;
  76. nodeData.highlighted = false;
  77. if(nodeData.originalFragment) {
  78. nodeData.node.setAttribute("contextMenu", "SyntaxifyPreUnwrapMenu");
  79. } else {
  80. nodeData.node.setAttribute("contextMenu", "SyntaxifyPreMenu");
  81. }
  82. }
  83.  
  84. function highlight() {
  85. var node = selectedItem;
  86. if(node) {
  87. if(!codeBlocks[node.id]) {
  88. addBlock(node);
  89. }
  90. var nodeData = codeBlocks[node.id];
  91. if(nodeData.highlighted === false) {
  92. nodeData.originalNode = node.cloneNode(true);
  93. try {
  94. if(nodeData.lang) {
  95. node.className = (node.className || "") + " lang-" + nodeData.lang;
  96. }
  97. hljs.highlightBlock(node);
  98. } catch(e) {
  99. console.err(e);
  100. revert(nodeData);
  101. alert(e);
  102. return;
  103. }
  104. nodeData.highlighted = true;
  105. node.setAttribute("contextMenu", "SyntaxifyPostMenu");
  106. }
  107. }
  108. }
  109.  
  110. function highlightAs() {
  111. var node = selectedItem;
  112. if(node) {
  113. if(!codeBlocks[node.id]) {
  114. addBlock(node);
  115. }
  116. var lang = prompt("Language:");
  117. if(lang) {
  118. codeBlocks[node.id].lang = lang;
  119. highlight();
  120. } else {
  121. codeBlocks[node.id].lang = null;
  122. }
  123. }
  124. }
  125.  
  126. function unHighlight() {
  127. var node = selectedItem;
  128. if(node) {
  129. if(!codeBlocks[node.id]) {
  130. addBlock(node);
  131. }
  132. var nodeData = codeBlocks[node.id];
  133. if(nodeData.highlighted === true) {
  134. revert(nodeData);
  135. }
  136. }
  137. }
  138.  
  139. var syntaxableElements = [];
  140. for(var i=0; i<tagsToSearch.length; i++) {
  141. var tagsFound = document.getElementsByTagName(tagsToSearch[i]);
  142. for(var j=0; j<tagsFound.length; j++) {
  143. syntaxableElements.push(tagsFound.item(j));
  144. }
  145. }
  146.  
  147. if(syntaxableElements) {
  148. for(var i=0; i<(syntaxableElements.length || 0); i++) {
  149. syntaxableElements[i].setAttribute("contextmenu", "SyntaxifyPreMenu");
  150. }
  151. }
  152.  
  153. var body = document.body;
  154. var bodyMenu;
  155. var bodyMenuId = body.getAttribute("contextmenu");
  156. if(!bodyMenuId) {
  157. bodyMenu = document.createElement("menu");
  158. bodyMenu.setAttribute("type", "context");
  159. bodyMenu.setAttribute("id", "SyntaxifyMenuMain");
  160. } else {
  161. bodyMenu = document.getElementById(bodyMenuId);
  162. }
  163.  
  164. var wrapItem = document.createElement("menuitem");
  165. wrapItem.setAttribute("label", "Syntaxify - Wrap with <code>");
  166. wrapItem.addEventListener("click", wrapSelection, false);
  167.  
  168. bodyMenu.appendChild(wrapItem);
  169. body.appendChild(bodyMenu);
  170. body.setAttribute("contextmenu", bodyMenu.getAttribute("id"));
  171.  
  172. // Menu for un-highlighted code blocks
  173. var syntaxPreMenu = document.createElement("menu");
  174. syntaxPreMenu.setAttribute("type", "context");
  175. syntaxPreMenu.setAttribute("id", "SyntaxifyPreMenu");
  176.  
  177. var syntaxPreMenuHighlightItem = document.createElement("menuitem");
  178. syntaxPreMenuHighlightItem.setAttribute("label", "Syntaxify - Highlight");
  179. syntaxPreMenuHighlightItem.addEventListener("click", highlight, false);
  180. syntaxPreMenu.appendChild(syntaxPreMenuHighlightItem);
  181.  
  182. var syntaxPreMenuHighlightAsItem = document.createElement("menuitem");
  183. syntaxPreMenuHighlightAsItem.setAttribute("label", "Syntaxify - Highlight as...");
  184. syntaxPreMenuHighlightAsItem.addEventListener("click", highlightAs, false);
  185. syntaxPreMenu.appendChild(syntaxPreMenuHighlightAsItem);
  186.  
  187. body.appendChild(syntaxPreMenu);
  188.  
  189.  
  190. // Menu for un-highlighted ad-hoc code blocks created via the context-menu's wrap entry
  191. var syntaxPreUnwrapMenu = syntaxPreMenu.cloneNode(false);
  192. syntaxPreUnwrapMenu.setAttribute("id", "SyntaxifyPreUnwrapMenu");
  193.  
  194. var syntaxPreUnwrapMenuUnwrapItem = document.createElement("menuitem");
  195. syntaxPreUnwrapMenuUnwrapItem.setAttribute("label", "Syntaxify - Unwrap");
  196. syntaxPreUnwrapMenuUnwrapItem.addEventListener("click", unwrap, false);
  197.  
  198. var syntaxPreUnwrapMenuHighlightItem = syntaxPreMenuHighlightItem.cloneNode(false);
  199. var syntaxPreUnwrapMenuHighlightAsItem = syntaxPreMenuHighlightAsItem.cloneNode(false);
  200.  
  201. syntaxPreUnwrapMenuHighlightItem.addEventListener("click", highlight, false);
  202. syntaxPreUnwrapMenuHighlightAsItem.addEventListener("click", highlightAs, false);
  203.  
  204. syntaxPreUnwrapMenu.appendChild(syntaxPreUnwrapMenuHighlightItem);
  205. syntaxPreUnwrapMenu.appendChild(syntaxPreUnwrapMenuHighlightAsItem);
  206. syntaxPreUnwrapMenu.appendChild(syntaxPreUnwrapMenuUnwrapItem);
  207.  
  208. body.appendChild(syntaxPreUnwrapMenu);
  209.  
  210.  
  211. var syntaxPostMenu = document.createElement("menu");
  212. syntaxPostMenu.setAttribute("type", "context");
  213. syntaxPostMenu.setAttribute("id", "SyntaxifyPostMenu");
  214.  
  215. var syntaxPostMenuUnHighlightItem = document.createElement("menuitem");
  216. syntaxPostMenuUnHighlightItem.setAttribute("label", "Syntaxify - UnHighlight");
  217. syntaxPostMenuUnHighlightItem.addEventListener("click", unHighlight, false);
  218. syntaxPostMenu.appendChild(syntaxPostMenuUnHighlightItem);
  219.  
  220. body.appendChild(syntaxPostMenu);
  221.  
  222. document.addEventListener("mousedown", function(event) {
  223. if(event.button == 2) {
  224. var node = event.target;
  225. while(node.parentNode && !isCodeBlock(node)) {
  226. node = node.parentNode;
  227. }
  228. if(isCodeBlock(node)) {
  229. selectedItem = node;
  230. } else {
  231. selectedItem = null;
  232. }
  233. }
  234. });