[RMFM addons]代码高亮

【这是 Reading mode for mobile 的功能插件,无法单独工作】对代码块进行高亮

  1. // ==UserScript==
  2. // @name [RMFM addons]代码高亮
  3. // @namespace RMFM_code_highlight
  4. // @version 0.0.2
  5. // @description 【这是 Reading mode for mobile 的功能插件,无法单独工作】对代码块进行高亮
  6. // @author 稻米鼠
  7. // @icon https://i.v2ex.co/UuYzTkNus.png
  8. // @supportURL https://meta.appinn.net/t/23292
  9. // @contributionURL https://afdian.net/@daomishu
  10. // @contributionAmount 8.88
  11. // @antifeature payment 主脚本(Reading mode for mobile)为付费脚本
  12. // @match *://*/*
  13. // @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/highlight.min.js
  14. // @grant none
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19. const codeStyle = `pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
  20. pre > div.code-copy-button {
  21. position: absolute;
  22. top: 4px;
  23. right: 18px;
  24. width: 18px;
  25. height: 18px;
  26. background-color: rgba(255, 255, 255, .3);
  27. border: 1px solid rgba(255, 255, 255, .4);
  28. border-radius: 2px;
  29. }
  30. pre > div.code-copy-button:hover {
  31. background-color: rgba(255, 255, 255, .6);
  32. border: 1px solid rgba(255, 255, 255, .7);
  33. }
  34. pre > div.code-copy-button::before,
  35. pre > div.code-copy-button::after {
  36. content: ' ';
  37. box-sizing: border-box;
  38. position: absolute;
  39. width: 9px;
  40. height: 12px;
  41. border: 2px solid rgba(0, 0, 0, .5);
  42. border-radius: 2px;
  43. }
  44. pre > div.code-copy-button::before {
  45. left: 3px;
  46. top: 2px;
  47. }
  48. pre > div.code-copy-button::after {
  49. right: 3px;
  50. bottom: 2px;
  51. }`
  52. /**
  53. * 为代码块生成行号图片
  54. *
  55. * @param {string|number} num 行号的数字
  56. * @param {string} style 相关的样式,字号、行高和字体
  57. * @return {string} 返回 base64 格式的图片地址
  58. */
  59. const generateLineNumber = (num, style)=>{
  60. const canvas = document.createElement('canvas')
  61. const ctx = canvas.getContext('2d')
  62. const context = {
  63. font: style.fontSize + ' ' + (style.fontFamily ? style.fontFamily : window.getComputedStyle(document.body).fontFamily),
  64. textAlign: "right",
  65. fillStyle: "#999999",
  66. }
  67. for(const s in context){ ctx[s] = context[s] }
  68.  
  69. style.width = ctx.measureText(num).width
  70. style.height = Number(style.lineHeight.replace('px', ''))
  71.  
  72. canvas.width = style.width
  73. canvas.height = style.height
  74.  
  75. for(const s in context){ ctx[s] = context[s] }
  76.  
  77. const textY = style.height/2+Number(style.fontSize.replace('px', ''))/2
  78. ctx.fillText(num, style.width, textY)
  79. return canvas.toDataURL("image/png")
  80. }
  81. /**
  82. * 为代码块设置行号
  83. *
  84. * @param {HTMLElement} codeEl
  85. */
  86. const setLineNumber = (codeEl)=>{
  87. const codeEls = codeEl ? [codeEl] : []
  88. codeEls.forEach(cEl=>{
  89. const cElStyle = window.getComputedStyle(cEl)
  90. const style = {
  91. fontSize: cElStyle.fontSize,
  92. fontFamily: cElStyle.fontFamily,
  93. lineHeight: cElStyle.lineHeight,
  94. // lineHeight: cElStyle.fontSize.replace('px', '')*1.2+'px',
  95. }
  96. cEl.innerHTML = cEl.innerHTML
  97. .split(/\n|<br[^>]?>/gi)
  98. .map((lineCode, index)=>{
  99. const lineNum = index+1
  100. const lineBG = generateLineNumber(lineNum, style)
  101. return '<div class="rmfm-code-line"><div class="rmfm-code-line-num" date-line-num="'+lineNum+'" style="background-image: url('+lineBG+')"></div>'+lineCode+'</div>'
  102. })
  103. .join('')
  104. })
  105. }
  106. const copyParentText = function(){
  107. const textArea = document.createElement('textarea')
  108. textArea.setAttribute('readonly', 'readonly')
  109. textArea.value = this.parentElement.innerText
  110. document.body.appendChild(textArea)
  111. textArea.select()
  112. document.execCommand('copy')
  113. document.body.removeChild(textArea)
  114. alert('Code is copied.')
  115. }
  116.  
  117. const opt = window.RMFM ? window.RMFM : window.RMFM={}
  118. const addons = (opt.addons && opt.addons instanceof Array) ? opt.addons : opt.addons=[]
  119. const eventRemver = []
  120. addons.push({
  121. name: 'Code block highlight',
  122. contentStyle: codeStyle,
  123. contentFilter: (glo, contentEl)=>{
  124. contentEl.querySelectorAll('pre').forEach(pre=>{
  125. const codeEl = pre.querySelector('code')
  126. const copyButton = document.createElement('div')
  127. copyButton.className = 'code-copy-button'
  128. pre.appendChild(copyButton)
  129. copyButton.addEventListener('click', copyParentText)
  130. eventRemver.push(()=>{
  131. copyButton.removeEventListener('click', copyParentText)
  132. })
  133. if(typeof(hljs) === "undefined") return
  134. hljs.highlightBlock(codeEl)
  135. setLineNumber(codeEl)
  136. })
  137. },
  138. onMainHide: ()=>{
  139. while(eventRemver.length){
  140. (eventRemver.shift())()
  141. }
  142. }
  143. })
  144. })()