index.js 7.7 KB

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