index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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. if (state.content.mermaid) {
  92. dom.push(m('script', {
  93. src: 'https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.8.4/mermaid.min.js'
  94. }))
  95. dom.push(m('script', {type: 'text/javascript'}, `
  96. ;(() => {
  97. var timeout = setInterval(() => {
  98. if (!!(mermaid && mermaid.init)) {
  99. clearInterval(timeout)
  100. mermaid.init({}, 'code.language-mmd, code.language-mermaid')
  101. }
  102. }, 50)
  103. })()
  104. `))
  105. }
  106. }
  107. }
  108. return (dom.length ? dom : m('div'))
  109. }
  110. })
  111. }
  112. var scroll = (() => {
  113. function race (done) {
  114. var images = Array.from(document.querySelectorAll('img'))
  115. if (!images.length) {
  116. done()
  117. }
  118. var loaded = 0
  119. images.forEach((img) => {
  120. img.addEventListener('load', () => {
  121. if (++loaded === images.length) {
  122. done()
  123. }
  124. }, {once: true})
  125. })
  126. setTimeout(done, 100)
  127. }
  128. function debounce (container, done) {
  129. var listener = /body/i.test(container.nodeName) ? window : container
  130. var timeout = null
  131. listener.addEventListener('scroll', () => {
  132. clearTimeout(timeout)
  133. timeout = setTimeout(done, 100)
  134. })
  135. }
  136. function listen (container, prefix) {
  137. var key = prefix + location.origin + location.pathname
  138. try {
  139. container.scrollTop = parseInt(localStorage.getItem(key))
  140. debounce(container, () => {
  141. localStorage.setItem(key, container.scrollTop)
  142. })
  143. }
  144. catch (err) {
  145. chrome.storage.local.get(key, (res) => {
  146. container.scrollTop = parseInt(res[key])
  147. })
  148. debounce(container, () => {
  149. chrome.storage.local.set({[key]: container.scrollTop})
  150. })
  151. }
  152. }
  153. return {
  154. body: () => {
  155. var loaded
  156. race(() => {
  157. if (!loaded) {
  158. loaded = true
  159. if (state.content.scroll) {
  160. listen($('body'), 'md-')
  161. }
  162. else if (location.hash && $(location.hash)) {
  163. $('body').scrollTop = $(location.hash).offsetTop
  164. }
  165. }
  166. })
  167. },
  168. toc: () => {
  169. listen($('#_toc'), 'md-toc-')
  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.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  193. }))
  194. .reduce((html, header) => {
  195. html += '<div class="_ul">'.repeat(header.level)
  196. html += link(header)
  197. html += '</div>'.repeat(header.level)
  198. return html
  199. }, '')
  200. if (document.readyState === 'complete') {
  201. mount()
  202. }
  203. else {
  204. var timeout = setInterval(() => {
  205. if (document.readyState === 'complete') {
  206. clearInterval(timeout)
  207. mount()
  208. }
  209. }, 0)
  210. }
  211. if (state.content.autoreload) {
  212. ;(() => {
  213. var initial = ''
  214. var response = (body) => {
  215. if (!initial) {
  216. initial = body
  217. }
  218. else if (initial !== body) {
  219. location.reload(true)
  220. }
  221. }
  222. var xhr = new XMLHttpRequest()
  223. xhr.onreadystatechange = () => {
  224. if (xhr.readyState === 4) {
  225. response(xhr.responseText)
  226. }
  227. }
  228. var get = () => {
  229. if (location.protocol === 'file:') {
  230. chrome.runtime.sendMessage({
  231. message: 'autoreload',
  232. location: location.href
  233. }, (res) => {
  234. if (res.err) {
  235. console.error(res.err)
  236. clearInterval(state.interval)
  237. }
  238. else {
  239. response(res.body)
  240. }
  241. })
  242. }
  243. else {
  244. xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
  245. try {
  246. xhr.send()
  247. }
  248. catch (err) {
  249. console.error(err)
  250. clearInterval(state.interval)
  251. }
  252. }
  253. }
  254. get()
  255. state.interval = setInterval(get, state.ms)
  256. })()
  257. }
  258. var mathjax = `
  259. // TeX-AMS_HTML
  260. MathJax.Hub.Config({
  261. jax: [
  262. 'input/TeX',
  263. 'output/HTML-CSS',
  264. 'output/PreviewHTML',
  265. ],
  266. extensions: [
  267. 'tex2jax.js',
  268. 'AssistiveMML.js',
  269. 'a11y/accessibility-menu.js',
  270. ],
  271. TeX: {
  272. extensions: [
  273. 'AMSmath.js',
  274. 'AMSsymbols.js',
  275. 'noErrors.js',
  276. 'noUndefined.js',
  277. ]
  278. },
  279. tex2jax: {
  280. inlineMath: [
  281. ['$', '$'],
  282. ['\\\\(', '\\\\)'],
  283. ],
  284. displayMath: [
  285. ['$$', '$$'],
  286. ['\\\\[', '\\\\]'],
  287. ],
  288. processEscapes: true
  289. },
  290. showMathMenu: false,
  291. showProcessingMessages: false,
  292. messageStyle: 'none',
  293. skipStartupTypeset: true, // disable initial rendering
  294. positionToHash: false
  295. })
  296. // set specific container to render, can be delayed too
  297. MathJax.Hub.Queue(
  298. ['Typeset', MathJax.Hub, '_html']
  299. )
  300. `