index.js 6.5 KB

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