index.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. var $ = document.querySelector.bind(document)
  2. var state = {
  3. theme: args.theme,
  4. raw: args.raw,
  5. themes: args.themes,
  6. content: args.content,
  7. compiler: args.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', {
  94. src: chrome.runtime.getURL('/vendor/mathjax/mathjax.init.js')
  95. }))
  96. dom.push(m('script', {
  97. src: chrome.runtime.getURL('/vendor/mathjax/tex-mml-chtml.js')
  98. }))
  99. }
  100. }
  101. }
  102. return (dom.length ? dom : m('div'))
  103. }
  104. })
  105. }
  106. var scroll = (() => {
  107. function race (done) {
  108. Promise.race([
  109. Promise.all([
  110. new Promise((resolve) => {
  111. var diagrams = Array.from(document.querySelectorAll('code.language-mmd, code.language-mermaid'))
  112. if (!state.content.mermaid || !diagrams.length) {
  113. resolve()
  114. }
  115. else {
  116. var timeout = setInterval(() => {
  117. var svg = Array.from(document.querySelectorAll('code.language-mmd svg, code.language-mermaid svg'))
  118. if (diagrams.length === svg.length) {
  119. clearInterval(timeout)
  120. resolve()
  121. }
  122. }, 50)
  123. }
  124. }),
  125. new Promise((resolve) => {
  126. var images = Array.from(document.querySelectorAll('img'))
  127. if (!images.length) {
  128. resolve()
  129. }
  130. else {
  131. var loaded = 0
  132. images.forEach((img) => {
  133. img.addEventListener('load', () => {
  134. if (++loaded === images.length) {
  135. resolve()
  136. }
  137. }, {once: true})
  138. })
  139. }
  140. }),
  141. ]),
  142. new Promise((resolve) => setTimeout(resolve, 500))
  143. ])
  144. .then(done)
  145. }
  146. function debounce (container, done) {
  147. var listener = /html|body/i.test(container.nodeName) ? window : container
  148. var timeout = null
  149. listener.addEventListener('scroll', () => {
  150. clearTimeout(timeout)
  151. timeout = setTimeout(done, 100)
  152. })
  153. }
  154. function listen (container, prefix) {
  155. var key = prefix + location.origin + location.pathname
  156. try {
  157. container.scrollTop = parseInt(localStorage.getItem(key))
  158. debounce(container, () => {
  159. localStorage.setItem(key, container.scrollTop)
  160. })
  161. }
  162. catch (err) {
  163. chrome.storage.local.get(key, (res) => {
  164. container.scrollTop = parseInt(res[key])
  165. })
  166. debounce(container, () => {
  167. chrome.storage.local.set({[key]: container.scrollTop})
  168. })
  169. }
  170. }
  171. return {
  172. body: () => {
  173. var loaded
  174. race(() => {
  175. if (!loaded) {
  176. loaded = true
  177. var container = ((html = $('html')) => (
  178. html.scrollTop = 1,
  179. html.scrollTop ? (html.scrollTop = 0, html) : $('body')
  180. ))()
  181. if (state.content.scroll) {
  182. listen(container, 'md-')
  183. }
  184. else if (location.hash && $(location.hash)) {
  185. container.scrollTop = $(location.hash).offsetTop
  186. }
  187. }
  188. })
  189. },
  190. toc: () => {
  191. listen($('#_toc'), 'md-toc-')
  192. }
  193. }
  194. })()
  195. function anchors () {
  196. Array.from($('#_html').childNodes)
  197. .filter((node) => /h[1-6]/i.test(node.tagName))
  198. .forEach((node) => {
  199. var a = document.createElement('a')
  200. a.className = 'anchor'
  201. a.name = node.id
  202. a.href = '#' + node.id
  203. a.innerHTML = '<span class="octicon octicon-link"></span>'
  204. node.prepend(a)
  205. })
  206. }
  207. var toc = (
  208. link = (header) => '<a href="#' + header.id + '">' + header.title + '</a>') =>
  209. Array.from($('#_html').childNodes)
  210. .filter((node) => /h[1-6]/i.test(node.tagName))
  211. .map((node) => ({
  212. id: node.getAttribute('id'),
  213. level: parseInt(node.tagName.replace('H', '')),
  214. title: node.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  215. }))
  216. .reduce((html, header) => {
  217. html += '<div class="_ul">'.repeat(header.level)
  218. html += link(header)
  219. html += '</div>'.repeat(header.level)
  220. return html
  221. }, '')
  222. if (document.readyState === 'complete') {
  223. mount()
  224. }
  225. else {
  226. var timeout = setInterval(() => {
  227. if (document.readyState === 'complete') {
  228. clearInterval(timeout)
  229. mount()
  230. }
  231. }, 0)
  232. }
  233. if (state.content.autoreload) {
  234. ;(() => {
  235. var initial = ''
  236. var response = (body) => {
  237. if (!initial) {
  238. initial = body
  239. }
  240. else if (initial !== body) {
  241. location.reload(true)
  242. }
  243. }
  244. var xhr = new XMLHttpRequest()
  245. xhr.onreadystatechange = () => {
  246. if (xhr.readyState === 4) {
  247. response(xhr.responseText)
  248. }
  249. }
  250. var get = () => {
  251. if (location.protocol === 'file:') {
  252. chrome.runtime.sendMessage({
  253. message: 'autoreload',
  254. location: location.href
  255. }, (res) => {
  256. if (res.err) {
  257. console.error(res.err)
  258. clearInterval(state.interval)
  259. }
  260. else {
  261. response(res.body)
  262. }
  263. })
  264. }
  265. else {
  266. xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
  267. try {
  268. xhr.send()
  269. }
  270. catch (err) {
  271. console.error(err)
  272. clearInterval(state.interval)
  273. }
  274. }
  275. }
  276. get()
  277. state.interval = setInterval(get, state.ms)
  278. })()
  279. }