index.js 6.3 KB

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