content.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. var $ = document.querySelector.bind(document)
  2. var state = {
  3. theme,
  4. raw,
  5. content,
  6. compiler,
  7. html: '',
  8. markdown: '',
  9. toc: '',
  10. interval: null,
  11. }
  12. chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
  13. if (req.message === 'reload') {
  14. location.reload(true)
  15. }
  16. else if (req.message === 'theme') {
  17. state.theme = req.theme
  18. m.redraw()
  19. }
  20. else if (req.message === 'raw') {
  21. state.raw = req.raw
  22. m.redraw()
  23. }
  24. else if (req.message === 'autoreload') {
  25. clearInterval(state.interval)
  26. }
  27. })
  28. var oncreate = {
  29. markdown: () => {
  30. scroll()
  31. },
  32. html: () => {
  33. scroll()
  34. if (state.content.toc && !state.toc) {
  35. state.toc = toc()
  36. m.redraw()
  37. }
  38. setTimeout(() => Prism.highlightAll(), 20)
  39. anchors()
  40. }
  41. }
  42. function mount () {
  43. $('pre').style.display = 'none'
  44. var md = $('pre').innerText
  45. m.mount($('body'), {
  46. oninit: () => {
  47. state.markdown = md
  48. chrome.runtime.sendMessage({
  49. message: 'markdown',
  50. compiler: state.compiler,
  51. markdown: state.markdown
  52. }, (res) => {
  53. state.html = state.content.emoji ? emojinator(res.html) : res.html
  54. m.redraw()
  55. })
  56. },
  57. view: () => {
  58. var dom = []
  59. if (state.raw) {
  60. dom.push(m('pre#_markdown', {oncreate: oncreate.markdown}, state.markdown))
  61. $('body').classList.remove('_toc-left', '_toc-right')
  62. }
  63. else {
  64. if (state.theme) {
  65. dom.push(m('link#_theme', {
  66. rel: 'stylesheet', type: 'text/css',
  67. href: chrome.runtime.getURL('/themes/' + state.theme + '.css')
  68. }))
  69. }
  70. if (state.html) {
  71. dom.push(m('#_html', {oncreate: oncreate.html,
  72. class: /github(-dark)?/.test(state.theme) ? 'markdown-body' : 'markdown-theme'},
  73. m.trust(state.html)
  74. ))
  75. if (state.content.toc && state.toc) {
  76. dom.push(m.trust(state.toc))
  77. $('body').classList.add('_toc-left')
  78. }
  79. if (state.content.mathjax) {
  80. dom.push(m('script', {type: 'text/x-mathjax-config',}, `
  81. // TeX-AMS_HTML
  82. MathJax.Hub.Config({
  83. jax: [
  84. 'input/TeX',
  85. 'output/HTML-CSS',
  86. 'output/PreviewHTML',
  87. ],
  88. extensions: [
  89. 'tex2jax.js',
  90. 'AssistiveMML.js',
  91. 'a11y/accessibility-menu.js',
  92. ],
  93. TeX: {
  94. extensions: [
  95. 'AMSmath.js',
  96. 'AMSsymbols.js',
  97. 'noErrors.js',
  98. 'noUndefined.js',
  99. ]
  100. },
  101. tex2jax: {
  102. inlineMath: [
  103. ['$', '$'],
  104. ['\\\\(', '\\\\)'],
  105. ],
  106. displayMath: [
  107. ['$$', '$$'],
  108. ['\\\\[', '\\\\]'],
  109. ],
  110. processEscapes: true
  111. },
  112. showMathMenu: false,
  113. showProcessingMessages: false,
  114. messageStyle: 'none',
  115. skipStartupTypeset: true, // disable initial rendering
  116. positionToHash: false
  117. })
  118. // set specific container to render, can be delayed too
  119. MathJax.Hub.Queue(
  120. ['Typeset', MathJax.Hub, '_html']
  121. )
  122. `))
  123. dom.push(m('script', {
  124. src: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js'
  125. }))
  126. }
  127. }
  128. }
  129. return (dom.length ? dom : m('div'))
  130. }
  131. })
  132. }
  133. function scroll () {
  134. function race (done) {
  135. var images = Array.from(document.querySelectorAll('img'))
  136. if (!images.length) {
  137. done()
  138. }
  139. var loaded = 0
  140. images.forEach((img) => {
  141. img.addEventListener('load', () => {
  142. if (++loaded === images.length) {
  143. done()
  144. }
  145. }, {once: true})
  146. })
  147. setTimeout(done, 100)
  148. }
  149. function init () {
  150. if (state.content.scroll) {
  151. var key = 'md-' + location.origin + location.pathname
  152. $('body').scrollTop = parseInt(localStorage.getItem(key))
  153. var timeout = null
  154. window.addEventListener('scroll', () => {
  155. clearTimeout(timeout)
  156. timeout = setTimeout(() => {
  157. localStorage.setItem(key, $('body').scrollTop)
  158. }, 100)
  159. })
  160. }
  161. else if (location.hash && $(location.hash)) {
  162. $('body').scrollTop = $(location.hash).offsetTop
  163. }
  164. }
  165. var loaded
  166. race(() => {
  167. if (!loaded) {
  168. init()
  169. loaded = true
  170. }
  171. })
  172. }
  173. function anchors () {
  174. Array.from($('#_html').childNodes)
  175. .filter((node) => /h[1-6]/i.test(node.tagName))
  176. .forEach((node) => {
  177. var a = document.createElement('a')
  178. a.className = 'anchor'
  179. a.name = node.id
  180. a.href = '#' + node.id
  181. a.innerHTML = '<span class="octicon octicon-link"></span>'
  182. node.prepend(a)
  183. })
  184. }
  185. var toc = (
  186. link = (header) => '<a href="#' + header.id + '">' + header.title + '</a>') =>
  187. Array.from($('#_html').childNodes)
  188. .filter((node) => /h[1-6]/i.test(node.tagName))
  189. .map((node) => ({
  190. id: node.getAttribute('id'),
  191. level: parseInt(node.tagName.replace('H', '')),
  192. title: node.innerText
  193. }))
  194. .reduce((html, header, index, headers) => {
  195. if (index) {
  196. var prev = headers[index - 1]
  197. }
  198. if (!index || prev.level === header.level) {
  199. html += link(header)
  200. }
  201. else if (prev.level > header.level) {
  202. while (prev.level-- > header.level) {
  203. html += '</div>'
  204. }
  205. html += link(header)
  206. }
  207. else if (prev.level < header.level) {
  208. while (prev.level++ < header.level) {
  209. html += '<div id="_ul">'
  210. }
  211. html += link(header)
  212. }
  213. return html
  214. }, '<div id="_toc"><div id="_ul">') + '</div></div>'
  215. if (document.readyState === 'complete') {
  216. mount()
  217. }
  218. else {
  219. window.addEventListener('DOMContentLoaded', mount)
  220. }
  221. if (state.content.autoreload) {
  222. ;(() => {
  223. var xhr = new XMLHttpRequest()
  224. xhr.onreadystatechange = () => {
  225. if (xhr.readyState === 4 && state.markdown !== xhr.responseText) {
  226. location.reload(true)
  227. }
  228. }
  229. state.interval = setInterval(() => {
  230. xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
  231. try {
  232. xhr.send()
  233. }
  234. catch (err) {
  235. console.error(err)
  236. clearInterval(state.interval)
  237. }
  238. }, 1000)
  239. })()
  240. }