content.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. var $ = document.querySelector.bind(document)
  2. var state = {
  3. theme,
  4. raw,
  5. content,
  6. compiler,
  7. html: '',
  8. markdown: '',
  9. toc: ''
  10. }
  11. chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
  12. if (req.message === 'reload') {
  13. location.reload(true)
  14. }
  15. else if (req.message === 'theme') {
  16. state.theme = req.theme
  17. m.redraw()
  18. }
  19. else if (req.message === 'raw') {
  20. state.raw = req.raw
  21. m.redraw()
  22. }
  23. else if (req.message === 'ping') {
  24. sendResponse({message: 'pong'})
  25. }
  26. })
  27. var oncreate = {
  28. markdown: () => {
  29. scroll()
  30. },
  31. html: () => {
  32. scroll()
  33. if (state.content.toc && !state.toc) {
  34. state.toc = toc()
  35. m.redraw()
  36. }
  37. setTimeout(() => Prism.highlightAll(), 20)
  38. anchors()
  39. }
  40. }
  41. function mount () {
  42. $('pre').style.display = 'none'
  43. var md = $('pre').innerText
  44. m.mount($('body'), {
  45. oninit: () => {
  46. ;((done) => {
  47. if (document.charset === 'UTF-8') {
  48. done()
  49. return
  50. }
  51. m.request({method: 'GET', url: location.href,
  52. deserialize: (body) => {
  53. done(body)
  54. return body
  55. }
  56. })
  57. })((data) => {
  58. state.markdown = data || md
  59. chrome.runtime.sendMessage({
  60. message: 'markdown',
  61. compiler: state.compiler,
  62. markdown: state.markdown
  63. }, (res) => {
  64. state.html = res.html
  65. m.redraw()
  66. })
  67. })
  68. },
  69. view: () => {
  70. var dom = []
  71. if (state.raw) {
  72. dom.push(m('pre#_markdown', {oncreate: oncreate.markdown}, state.markdown))
  73. $('body').classList.remove('_toc-left', '_toc-right')
  74. }
  75. else {
  76. if (state.theme) {
  77. dom.push(m('link#_theme [rel="stylesheet"] [type="text/css"]', {
  78. href: chrome.runtime.getURL('/themes/' + state.theme + '.css')
  79. }))
  80. }
  81. if (state.html) {
  82. dom.push(m('#_html', {oncreate: oncreate.html,
  83. class: /github(-dark)?/.test(state.theme) ? 'markdown-body' : 'markdown-theme'},
  84. m.trust(state.html)
  85. ))
  86. if (state.content.toc && state.toc) {
  87. dom.push(m.trust(state.toc))
  88. $('body').classList.add('_toc-left')
  89. }
  90. }
  91. }
  92. return (dom.length ? dom : m('div'))
  93. }
  94. })
  95. }
  96. function scroll () {
  97. function race (done) {
  98. var images = Array.from(document.querySelectorAll('img'))
  99. if (!images.length) {
  100. done()
  101. }
  102. var loaded = 0
  103. images.forEach((img) => {
  104. img.addEventListener('load', () => {
  105. if (++loaded === images.length) {
  106. done()
  107. }
  108. }, {once: true})
  109. })
  110. setTimeout(done, 100)
  111. }
  112. function init () {
  113. if (state.content.scroll) {
  114. var key = 'md-' + location.origin + location.pathname
  115. $('body').scrollTop = parseInt(localStorage.getItem(key))
  116. var timeout = null
  117. window.addEventListener('scroll', () => {
  118. clearTimeout(timeout)
  119. timeout = setTimeout(() => {
  120. localStorage.setItem(key, $('body').scrollTop)
  121. }, 100)
  122. })
  123. }
  124. else if (location.hash && $(location.hash)) {
  125. $('body').scrollTop = $(location.hash).offsetTop
  126. }
  127. }
  128. var loaded
  129. race(() => {
  130. if (!loaded) {
  131. init()
  132. loaded = true
  133. }
  134. })
  135. }
  136. function anchors () {
  137. Array.from($('#_html').childNodes)
  138. .filter((node) => /h[1-6]/i.test(node.tagName))
  139. .forEach((node) => {
  140. var a = document.createElement('a')
  141. a.className = 'anchor'
  142. a.name = node.id
  143. a.href = '#' + node.id
  144. a.innerHTML = '<span class="octicon octicon-link"></span>'
  145. node.prepend(a)
  146. })
  147. }
  148. var toc = (
  149. link = (header) => '<a href="#' + header.id + '">' + header.title + '</a>') =>
  150. Array.from($('#_html').childNodes)
  151. .filter((node) => /h[1-6]/i.test(node.tagName))
  152. .map((node) => ({
  153. id: node.getAttribute('id'),
  154. level: parseInt(node.tagName.replace('H', '')),
  155. title: node.innerText
  156. }))
  157. .reduce((html, header, index, headers) => {
  158. if (index) {
  159. var prev = headers[index - 1]
  160. }
  161. if (!index || prev.level === header.level) {
  162. html += link(header)
  163. }
  164. else if (prev.level > header.level) {
  165. while (prev.level-- > header.level) {
  166. html += '</div>'
  167. }
  168. html += link(header)
  169. }
  170. else if (prev.level < header.level) {
  171. while (prev.level++ < header.level) {
  172. html += '<div id="_ul">'
  173. }
  174. html += link(header)
  175. }
  176. return html
  177. }, '<div id="_toc"><div id="_ul">') + '</div></div>'
  178. if (document.readyState === 'complete') {
  179. mount()
  180. }
  181. else {
  182. window.addEventListener('DOMContentLoaded', mount)
  183. }