v2exMarkdown

为v2ex而生的markdown渲染

当前为 2019-04-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name v2exMarkdown
  3. // @version 0.67.2
  4. // @description 为v2ex而生的markdown渲染
  5. // @author hundan
  6. // @match https://*.v2ex.com/t/*
  7. // @require https://cdn.staticfile.org/showdown/1.8.6/showdown.min.js
  8. // @require https://cdn.staticfile.org/fancybox/3.3.5/jquery.fancybox.min.js
  9. // @grant none
  10. // @namespace https://github.com/hundan2020/v2exMarkdown
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. // jquery.js和highlight.js都由v2ex自身提供,不再向外部重复请求
  15. // 预处理以解决与 v2ex plus 的冲突
  16. $(".reply_content img").each(function(){
  17. var $this = $(this)
  18. if ($this[0].src.indexOf(".sinaimg.cn") != -1 && $this[0].src.indexOf("http://") != -1) {
  19. $this[0].src = "https" + $this[0].src.substr(4)
  20. }
  21. })
  22. // markdown处理
  23. var preFix = function(rawReply){
  24. var picRe = function(reply){
  25. reply = reply.replace(/(?:!\[.*?\])?!\[.*?\]\(\s*((?:https?)?:\/\/i\.loli\.net\/\d{4}\/\d{2}\/\d{2}\/[a-z0-9]+.[a-z]+)\)|(https:\/\/i\.loli\.net\/\d{4}\/\d{2}\/\d{2}\/[a-z0-9]+.[a-z]+)/ig, '![]( ' + encodeURI('$1$2') + ' )') // sm.ms
  26. reply = reply.replace(/(?:!\[.*?\])?!\[.*?\]\(\s*((?:https?)?:\/\/imgurl\.org\/temp\/\d{4}\/[a-z0-9]+\.[a-z0-9]+)\)|(https?:\/\/imgurl\.org\/temp\/\d{4}\/[a-z0-9]+\.[a-z0-9]+)/ig, '![]( ' + encodeURI('$1$2') + ' )') // 小z图床
  27. reply = reply.replace(/(?<!http:|https:)\/\/([a-z0-9]+\.sinaimg.cn\/(?:[a-z]+)\/[a-z0-9]+\.(?:jpg|png|gif|bmp))/ig, '![]( ' + encodeURI('https://$1') + ' )') // 新浪微博不规则图片链接
  28. //reply = reply.replace(/(?<!http:|https:)\/\/([a-z0-9]+\.sinaimg.cn\/(?:[a-z]+)\/[a-z0-9]+\.(?:jpg|png|gif|bmp))/ig, '![]( ' + encodeURI('https://$1') + ' )') // 新浪微博不规则图片链接
  29. return reply
  30. }
  31. var xssFilter = function(reply){
  32. var sReply = reply
  33. sReply = sReply.replace(/(!?\[.*?\]\(\s*?javascript.*?\))/igs, '`$1`')
  34. return sReply
  35. }
  36. var fixedReply = rawReply
  37. fixedReply = fixedReply.replace(/#(\d{1,3}\s)/ig, '&#x23;$1 ') // 避免楼层号加粗 safe
  38. fixedReply = fixedReply.replace(/(!\[(\S*?)\]\(\s+?)<a target="_blank" href="(\S+?)".*?><img src="(\S+?)" class="embedded_image".*?><\/a>\)+/ig, '![$2]($3)') // 正常显示的图片处理 safe
  39. fixedReply = fixedReply.replace(/&lt;img src="(.+?)" \/&gt;/ig, '$1') // 不规则图片链接处理 safe
  40. fixedReply = fixedReply.replace(/@<a href="\/member\/(\S+?)">(\S+?)<\/a>/ig, '@[$1](/member/$2)') // 论坛内@处理,考虑到代码段中的@应当正常显示 safe
  41. fixedReply = fixedReply.replace(/<a target="_blank" href="(\/t\/\d+)"\s*?(?:rel="nofollow")?>\/t\/\d+<\/a>/ig, '[$1]($1)') // 论坛内链处理,考虑到在代码段中应当正常显示 safe
  42. fixedReply = fixedReply.replace(/<a.*? href="(\S+?)".*?>(\S+?)<\/a>/ig, '$2') // 链接处理 safe
  43. fixedReply = fixedReply.replace(/\[!\[(\S+?)\]\(\s*(\S+?)\)\]\(\s*\S+?\)/ig, '![$1]($2)') // 不规则图片链接处理,不规则案例见 `https://www.v2ex.com/t/463469#r_5792042`
  44. fixedReply = fixedReply.replace(/(\n)?<br *\/?>/ig, "\n") // 换行处理,避免多行代码无法正常工作 safe
  45. fixedReply = picRe(fixedReply)
  46. fixedReply = xssFilter(fixedReply)
  47. return fixedReply
  48. }
  49. var endFix = function(markedReply){
  50. var fixedReply = markedReply
  51. fixedReply = fixedReply.replace(/\n/ig, '<br />') //safe markdown软回车转硬回车
  52. fixedReply = fixedReply.replace(/(<\/ul>|<\/li>|<\/p>|<\/table>|<\/h\d>)\s*<br\s*\/?>/ig, '$1') // safe 表格换行删除
  53. fixedReply = fixedReply.replace(/<br\s*\/?>(<li>|<ul>|<p>|<table>|<h\d>)/ig, '$1') // safe 表格换行删除
  54. fixedReply = fixedReply.replace(/(<\/?table>|<\/?tbody>|<\/?thead>|<\/?tr>|<\/?th>|<\/?td>)<br\s*\/?>/ig, '$1') // safe 表格换行删除
  55. fixedReply = fixedReply.replace(/(<br\s*\/?>\s*){2,}/ig, '<br />') // safe 多重换行转单行
  56. fixedReply = fixedReply.replace(/@\[(\S+?)\]\(\/member\/\S+\)/ig, '@$1') // 代码段中的@ 还原
  57. fixedReply = fixedReply.replace(/\[(\/t\/\d+)\]\(\/t\/\d+\)/ig, '$1') // 代码段中的内链还原
  58. fixedReply = fixedReply.replace(/&amp;/ig, '&') // 对重复转义的 & 进行还原,而不必对<>进行操作,有效的避免了XSS发生
  59. return fixedReply
  60. }
  61. var processMarkdown = function(){
  62. $("div.reply_content").each(function () {
  63. var reply = $(this)[0]
  64. var rawReply = reply.innerHTML
  65. var converter = new showdown.Converter({
  66. omitExtraWLInCodeBlocks: true,
  67. parseImgDimensions: true,
  68. simplifiedAutoLink: true,
  69. literalMidWordUnderscores: true,
  70. strikethrough: true,
  71. tables: true,
  72. ghCodeBlocks: true,
  73. tasklists: true,
  74. smoothLivePreview: true,
  75. ghCompatibleHeaderId: true,
  76. encodeEmails: true,
  77. emoji: true
  78. })
  79. var markedReply = converter.makeHtml(preFix(rawReply))
  80. reply.innerHTML = endFix(markedReply)
  81. reply.className = 'reply_content markdown_body'
  82. // 开启代码高亮
  83. hljs.configure({useBR: true})
  84. $('div.reply_content code').each(function(i, block) {
  85. hljs.highlightBlock(block)
  86. })
  87. })
  88. }
  89. processMarkdown()
  90. // 加载看图插件
  91. function loadStyle(url){
  92. var link = document.createElement('link')
  93. link.type = 'text/css'
  94. link.rel = 'stylesheet'
  95. link.href = url
  96. var head = document.getElementsByTagName('head')[0]
  97. head.appendChild(link)
  98. }
  99. loadStyle('https://cdn.staticfile.org/fancybox/3.3.5/jquery.fancybox.min.css')
  100. var a_wrap = $('<a></a>')
  101. var imgs = $('.markdown_body img, .topic_content img')
  102. imgs.wrap('<a></a>')
  103. $.each(imgs, function (i, j) {
  104. $(j).css({'max-height':'25vh'})
  105. // let a_src = $(this).attr('src').replace(/^(?:https?:)?(?:\/\/)?(\w+.sinaimg.cn\/)/, "https://$1")
  106. $(j).parent().attr('id', 'img' + i).attr('href', $(this).attr('src'))
  107. console.log($(this).attr('src'))
  108. // 通过延迟解决与v2ex plus的冲突
  109. setTimeout(function(){
  110. $("#img" + i).fancybox({
  111. buttons: [
  112. "slideShow",
  113. "thumbs",
  114. "close"
  115. ],
  116. 'zoomSpeedIn': 300,
  117. 'zoomSpeedOut': 300,
  118. 'overlayShow': false,
  119. 'overlayOpacity': 0.3
  120. })
  121. },500)
  122. })
  123. // 添加楼主标记
  124. let author = $('#Main > div:nth-child(2) > div.header > small > a').text()
  125. $('table > tbody > tr > td > strong > a').each(function(){
  126. if ($(this).text() == author){
  127. var sign = $('<span></span>')
  128. sign.text('楼主')
  129. sign.css({"padding": "1px 5px", "margin": "0 6px", "font-size": "x-small", "color": "#777", "border-radius": "8px", "border": "1px solid"})
  130. $(this).parent().after(sign)
  131. }
  132. })
  133. // 修复新浪外链
  134. $("img").each(function(){
  135. })
  136. window.refererBypass = `
  137. <script src="https://cdn.staticfile.org/jquery/3.4.0/jquery.min.js">
  138. <\/script>
  139. <script>
  140. $(parent.document).find("img").each(function(){
  141. //let img_src = $(this).attr("src").replace(/^(?:https?:)?(?:\\\/\\\/)?(\\w+.sinaimg.cn\\\/)/, "https:\/\/\$1")
  142. let img_src = $(this).attr("src")
  143. let preload_img = \`<img src="\$\{img_src\}" />\`
  144. document.write(preload_img)
  145. })
  146. $('img').on('load', function () {
  147. //console.log(this.src)
  148. $(parent.document).find('img[src$="'+this.src+'"]').each(function () {
  149. this.src = this.src
  150. })
  151. })
  152. <\/script>
  153. `
  154. $('body').append('<iframe src="javascript:parent.refererBypass;" style="display: none;"></iframe>')
  155. $(function(){
  156. setTimeout(function(){
  157. console.clear()
  158. console.log("\n\n\n Thanks for using my script~\n\n\n\n")
  159. }, 2000)
  160. })
  161. })()