content.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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: chrome.runtime.getURL('/themes/' + state.theme + '.css')
  69. }))
  70. }
  71. if (state.html) {
  72. dom.push(m('#_html', {oncreate: oncreate.html,
  73. class: /github(-dark)?/.test(state.theme) ? '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',}, `
  82. // TeX-AMS_HTML
  83. MathJax.Hub.Config({
  84. jax: [
  85. 'input/TeX',
  86. 'output/HTML-CSS',
  87. 'output/PreviewHTML',
  88. ],
  89. extensions: [
  90. 'tex2jax.js',
  91. 'AssistiveMML.js',
  92. 'a11y/accessibility-menu.js',
  93. ],
  94. TeX: {
  95. extensions: [
  96. 'AMSmath.js',
  97. 'AMSsymbols.js',
  98. 'noErrors.js',
  99. 'noUndefined.js',
  100. ]
  101. },
  102. tex2jax: {
  103. inlineMath: [
  104. ['$', '$'],
  105. ['\\\\(', '\\\\)'],
  106. ],
  107. displayMath: [
  108. ['$$', '$$'],
  109. ['\\\\[', '\\\\]'],
  110. ],
  111. processEscapes: true
  112. },
  113. showMathMenu: false,
  114. showProcessingMessages: false,
  115. messageStyle: 'none',
  116. skipStartupTypeset: true, // disable initial rendering
  117. positionToHash: false
  118. })
  119. // set specific container to render, can be delayed too
  120. MathJax.Hub.Queue(
  121. ['Typeset', MathJax.Hub, '_html']
  122. )
  123. `))
  124. dom.push(m('script', {
  125. src: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js'
  126. }))
  127. }
  128. }
  129. }
  130. return (dom.length ? dom : m('div'))
  131. }
  132. })
  133. }
  134. function scroll () {
  135. function race (done) {
  136. var images = Array.from(document.querySelectorAll('img'))
  137. if (!images.length) {
  138. done()
  139. }
  140. var loaded = 0
  141. images.forEach((img) => {
  142. img.addEventListener('load', () => {
  143. if (++loaded === images.length) {
  144. done()
  145. }
  146. }, {once: true})
  147. })
  148. setTimeout(done, 100)
  149. }
  150. function init () {
  151. if (state.content.scroll) {
  152. var key = 'md-' + location.origin + location.pathname
  153. $('body').scrollTop = parseInt(localStorage.getItem(key))
  154. var timeout = null
  155. window.addEventListener('scroll', () => {
  156. clearTimeout(timeout)
  157. timeout = setTimeout(() => {
  158. localStorage.setItem(key, $('body').scrollTop)
  159. }, 100)
  160. })
  161. }
  162. else if (location.hash && $(location.hash)) {
  163. $('body').scrollTop = $(location.hash).offsetTop
  164. }
  165. }
  166. var loaded
  167. race(() => {
  168. if (!loaded) {
  169. init()
  170. loaded = true
  171. }
  172. })
  173. }
  174. function anchors () {
  175. Array.from($('#_html').childNodes)
  176. .filter((node) => /h[1-6]/i.test(node.tagName))
  177. .forEach((node) => {
  178. var a = document.createElement('a')
  179. a.className = 'anchor'
  180. a.name = node.id
  181. a.href = '#' + node.id
  182. a.innerHTML = '<span class="octicon octicon-link"></span>'
  183. node.prepend(a)
  184. })
  185. }
  186. var toc = (
  187. link = (header) => '<a href="#' + header.id + '">' + header.title + '</a>') =>
  188. Array.from($('#_html').childNodes)
  189. .filter((node) => /h[1-6]/i.test(node.tagName))
  190. .map((node) => ({
  191. id: node.getAttribute('id'),
  192. level: parseInt(node.tagName.replace('H', '')),
  193. title: node.innerText
  194. }))
  195. .reduce((html, header, index, headers) => {
  196. if (index) {
  197. var prev = headers[index - 1]
  198. }
  199. if (!index || prev.level === header.level) {
  200. html += link(header)
  201. }
  202. else if (prev.level > header.level) {
  203. while (prev.level-- > header.level) {
  204. html += '</div>'
  205. }
  206. html += link(header)
  207. }
  208. else if (prev.level < header.level) {
  209. while (prev.level++ < header.level) {
  210. html += '<div id="_ul">'
  211. }
  212. html += link(header)
  213. }
  214. return html
  215. }, '<div id="_toc"><div id="_ul">') + '</div></div>'
  216. if (document.readyState === 'complete') {
  217. mount()
  218. }
  219. else {
  220. window.addEventListener('DOMContentLoaded', mount)
  221. }
  222. if (state.content.autoreload) {
  223. ;(() => {
  224. var initial = ''
  225. var xhr = new XMLHttpRequest()
  226. xhr.onreadystatechange = () => {
  227. if (xhr.readyState === 4) {
  228. if (!initial) {
  229. initial = xhr.responseText
  230. }
  231. else if (initial !== xhr.responseText) {
  232. location.reload(true)
  233. }
  234. }
  235. }
  236. var get = () => {
  237. xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
  238. try {
  239. xhr.send()
  240. }
  241. catch (err) {
  242. console.error(err)
  243. clearInterval(state.interval)
  244. }
  245. }
  246. get()
  247. state.interval = setInterval(get, state.ms)
  248. })()
  249. }