index.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  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 (!!(window.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. Promise.race([
  122. Promise.all([
  123. new Promise((resolve) => {
  124. var diagrams = Array.from(document.querySelectorAll('code.language-mmd, code.language-mermaid'))
  125. if (!state.content.mermaid || !diagrams.length) {
  126. resolve()
  127. }
  128. else {
  129. var timeout = setInterval(() => {
  130. var svg = Array.from(document.querySelectorAll('code.language-mmd svg, code.language-mermaid svg'))
  131. if (diagrams.length === svg.length) {
  132. clearInterval(timeout)
  133. resolve()
  134. }
  135. }, 50)
  136. }
  137. }),
  138. new Promise((resolve) => {
  139. var images = Array.from(document.querySelectorAll('img'))
  140. if (!images.length) {
  141. resolve()
  142. }
  143. else {
  144. var loaded = 0
  145. images.forEach((img) => {
  146. img.addEventListener('load', () => {
  147. if (++loaded === images.length) {
  148. resolve()
  149. }
  150. }, {once: true})
  151. })
  152. }
  153. }),
  154. ]),
  155. new Promise((resolve) => setTimeout(resolve, 500))
  156. ])
  157. .then(done)
  158. }
  159. function debounce (container, done) {
  160. var listener = /html/i.test(container.nodeName) ? window : container
  161. var timeout = null
  162. listener.addEventListener('scroll', () => {
  163. clearTimeout(timeout)
  164. timeout = setTimeout(done, 100)
  165. })
  166. }
  167. function listen (container, prefix) {
  168. var key = prefix + location.origin + location.pathname
  169. try {
  170. container.scrollTop = parseInt(localStorage.getItem(key))
  171. debounce(container, () => {
  172. localStorage.setItem(key, container.scrollTop)
  173. })
  174. }
  175. catch (err) {
  176. chrome.storage.local.get(key, (res) => {
  177. container.scrollTop = parseInt(res[key])
  178. })
  179. debounce(container, () => {
  180. chrome.storage.local.set({[key]: container.scrollTop})
  181. })
  182. }
  183. }
  184. return {
  185. body: () => {
  186. var loaded
  187. race(() => {
  188. if (!loaded) {
  189. loaded = true
  190. if (state.content.scroll) {
  191. listen($('html'), 'md-')
  192. }
  193. else if (location.hash && $(location.hash)) {
  194. $('html').scrollTop = $(location.hash).offsetTop
  195. }
  196. }
  197. })
  198. },
  199. toc: () => {
  200. listen($('#_toc'), 'md-toc-')
  201. }
  202. }
  203. })()
  204. function anchors () {
  205. Array.from($('#_html').childNodes)
  206. .filter((node) => /h[1-6]/i.test(node.tagName))
  207. .forEach((node) => {
  208. var a = document.createElement('a')
  209. a.className = 'anchor'
  210. a.name = node.id
  211. a.href = '#' + node.id
  212. a.innerHTML = '<span class="octicon octicon-link"></span>'
  213. node.prepend(a)
  214. })
  215. }
  216. var toc = (
  217. link = (header) => '<a href="#' + header.id + '">' + header.title + '</a>') =>
  218. Array.from($('#_html').childNodes)
  219. .filter((node) => /h[1-6]/i.test(node.tagName))
  220. .map((node) => ({
  221. id: node.getAttribute('id'),
  222. level: parseInt(node.tagName.replace('H', '')),
  223. title: node.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  224. }))
  225. .reduce((html, header) => {
  226. html += '<div class="_ul">'.repeat(header.level)
  227. html += link(header)
  228. html += '</div>'.repeat(header.level)
  229. return html
  230. }, '')
  231. if (document.readyState === 'complete') {
  232. mount()
  233. }
  234. else {
  235. var timeout = setInterval(() => {
  236. if (document.readyState === 'complete') {
  237. clearInterval(timeout)
  238. mount()
  239. }
  240. }, 0)
  241. }
  242. if (state.content.autoreload) {
  243. ;(() => {
  244. var initial = ''
  245. var response = (body) => {
  246. if (!initial) {
  247. initial = body
  248. }
  249. else if (initial !== body) {
  250. location.reload(true)
  251. }
  252. }
  253. var xhr = new XMLHttpRequest()
  254. xhr.onreadystatechange = () => {
  255. if (xhr.readyState === 4) {
  256. response(xhr.responseText)
  257. }
  258. }
  259. var get = () => {
  260. if (location.protocol === 'file:') {
  261. chrome.runtime.sendMessage({
  262. message: 'autoreload',
  263. location: location.href
  264. }, (res) => {
  265. if (res.err) {
  266. console.error(res.err)
  267. clearInterval(state.interval)
  268. }
  269. else {
  270. response(res.body)
  271. }
  272. })
  273. }
  274. else {
  275. xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
  276. try {
  277. xhr.send()
  278. }
  279. catch (err) {
  280. console.error(err)
  281. clearInterval(state.interval)
  282. }
  283. }
  284. }
  285. get()
  286. state.interval = setInterval(get, state.ms)
  287. })()
  288. }
  289. var mathjax = `
  290. // TeX-AMS_HTML
  291. MathJax.Hub.Config({
  292. jax: [
  293. 'input/TeX',
  294. 'output/HTML-CSS',
  295. 'output/PreviewHTML',
  296. ],
  297. extensions: [
  298. 'tex2jax.js',
  299. 'AssistiveMML.js',
  300. 'a11y/accessibility-menu.js',
  301. ],
  302. TeX: {
  303. extensions: [
  304. 'AMSmath.js',
  305. 'AMSsymbols.js',
  306. 'noErrors.js',
  307. 'noUndefined.js',
  308. ]
  309. },
  310. tex2jax: {
  311. inlineMath: [
  312. ['$', '$'],
  313. ['\\\\(', '\\\\)'],
  314. ],
  315. displayMath: [
  316. ['$$', '$$'],
  317. ['\\\\[', '\\\\]'],
  318. ],
  319. processEscapes: true
  320. },
  321. showMathMenu: false,
  322. showProcessingMessages: false,
  323. messageStyle: 'none',
  324. skipStartupTypeset: true, // disable initial rendering
  325. positionToHash: false
  326. })
  327. // set specific container to render, can be delayed too
  328. MathJax.Hub.Queue(
  329. ['Typeset', MathJax.Hub, '_html']
  330. )
  331. `