index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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|body/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. var container = ((html = $('html')) => (
  191. html.scrollTop = 1,
  192. html.scrollTop ? (html.scrollTop = 0, html) : $('body')
  193. ))()
  194. if (state.content.scroll) {
  195. listen(container, 'md-')
  196. }
  197. else if (location.hash && $(location.hash)) {
  198. container.scrollTop = $(location.hash).offsetTop
  199. }
  200. }
  201. })
  202. },
  203. toc: () => {
  204. listen($('#_toc'), 'md-toc-')
  205. }
  206. }
  207. })()
  208. function anchors () {
  209. Array.from($('#_html').childNodes)
  210. .filter((node) => /h[1-6]/i.test(node.tagName))
  211. .forEach((node) => {
  212. var a = document.createElement('a')
  213. a.className = 'anchor'
  214. a.name = node.id
  215. a.href = '#' + node.id
  216. a.innerHTML = '<span class="octicon octicon-link"></span>'
  217. node.prepend(a)
  218. })
  219. }
  220. var toc = (
  221. link = (header) => '<a href="#' + header.id + '">' + header.title + '</a>') =>
  222. Array.from($('#_html').childNodes)
  223. .filter((node) => /h[1-6]/i.test(node.tagName))
  224. .map((node) => ({
  225. id: node.getAttribute('id'),
  226. level: parseInt(node.tagName.replace('H', '')),
  227. title: node.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  228. }))
  229. .reduce((html, header) => {
  230. html += '<div class="_ul">'.repeat(header.level)
  231. html += link(header)
  232. html += '</div>'.repeat(header.level)
  233. return html
  234. }, '')
  235. if (document.readyState === 'complete') {
  236. mount()
  237. }
  238. else {
  239. var timeout = setInterval(() => {
  240. if (document.readyState === 'complete') {
  241. clearInterval(timeout)
  242. mount()
  243. }
  244. }, 0)
  245. }
  246. if (state.content.autoreload) {
  247. ;(() => {
  248. var initial = ''
  249. var response = (body) => {
  250. if (!initial) {
  251. initial = body
  252. }
  253. else if (initial !== body) {
  254. location.reload(true)
  255. }
  256. }
  257. var xhr = new XMLHttpRequest()
  258. xhr.onreadystatechange = () => {
  259. if (xhr.readyState === 4) {
  260. response(xhr.responseText)
  261. }
  262. }
  263. var get = () => {
  264. if (location.protocol === 'file:') {
  265. chrome.runtime.sendMessage({
  266. message: 'autoreload',
  267. location: location.href
  268. }, (res) => {
  269. if (res.err) {
  270. console.error(res.err)
  271. clearInterval(state.interval)
  272. }
  273. else {
  274. response(res.body)
  275. }
  276. })
  277. }
  278. else {
  279. xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
  280. try {
  281. xhr.send()
  282. }
  283. catch (err) {
  284. console.error(err)
  285. clearInterval(state.interval)
  286. }
  287. }
  288. }
  289. get()
  290. state.interval = setInterval(get, state.ms)
  291. })()
  292. }
  293. var mathjax = `
  294. // TeX-AMS_HTML
  295. MathJax.Hub.Config({
  296. jax: [
  297. 'input/TeX',
  298. 'output/HTML-CSS',
  299. 'output/PreviewHTML',
  300. ],
  301. extensions: [
  302. 'tex2jax.js',
  303. 'AssistiveMML.js',
  304. 'a11y/accessibility-menu.js',
  305. ],
  306. TeX: {
  307. extensions: [
  308. 'AMSmath.js',
  309. 'AMSsymbols.js',
  310. 'noErrors.js',
  311. 'noUndefined.js',
  312. ]
  313. },
  314. tex2jax: {
  315. inlineMath: [
  316. ['$', '$'],
  317. ['\\\\(', '\\\\)'],
  318. ],
  319. displayMath: [
  320. ['$$', '$$'],
  321. ['\\\\[', '\\\\]'],
  322. ],
  323. processEscapes: true
  324. },
  325. showMathMenu: false,
  326. showProcessingMessages: false,
  327. messageStyle: 'none',
  328. skipStartupTypeset: true, // disable initial rendering
  329. positionToHash: false
  330. })
  331. // set specific container to render, can be delayed too
  332. MathJax.Hub.Queue(
  333. ['Typeset', MathJax.Hub, '_html']
  334. )
  335. `