index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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. _themes: {
  14. 'github': 'auto',
  15. 'github-dark': 'dark',
  16. 'almond': 'light',
  17. 'air': 'auto',
  18. 'awsm': 'light',
  19. 'axist': 'light',
  20. 'bamboo': 'auto',
  21. 'bullframe': 'light',
  22. 'holiday': 'auto',
  23. 'kacit': 'light',
  24. 'latex': 'light',
  25. 'marx': 'auto',
  26. 'mini': 'light',
  27. 'modest': 'auto',
  28. 'new': 'auto',
  29. 'no-class': 'auto',
  30. 'pico': 'auto',
  31. 'retro': 'dark',
  32. 'sakura': 'light',
  33. 'sakura-vader': 'dark',
  34. 'semantic': 'auto',
  35. 'simple': 'auto',
  36. 'splendor': 'auto',
  37. 'style-sans': 'light',
  38. 'style-serif': 'light',
  39. 'stylize': 'auto',
  40. 'superstylin': 'auto',
  41. 'tacit': 'light',
  42. 'vanilla': 'auto',
  43. 'water': 'light',
  44. 'water-dark': 'dark',
  45. 'writ': 'auto',
  46. }
  47. }
  48. chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
  49. if (req.message === 'reload') {
  50. location.reload(true)
  51. }
  52. else if (req.message === 'theme') {
  53. state.theme = req.theme
  54. m.redraw()
  55. }
  56. else if (req.message === 'themes') {
  57. state.themes = req.themes
  58. m.redraw()
  59. }
  60. else if (req.message === 'raw') {
  61. state.raw = req.raw
  62. m.redraw()
  63. }
  64. else if (req.message === 'autoreload') {
  65. clearInterval(state.interval)
  66. }
  67. })
  68. var oncreate = {
  69. markdown: () => {
  70. scroll.body()
  71. },
  72. html: () => {
  73. scroll.body()
  74. if (state.content.toc && !state.toc) {
  75. state.toc = toc()
  76. m.redraw()
  77. }
  78. if (state.content.syntax) {
  79. setTimeout(() => Prism.highlightAll(), 20)
  80. }
  81. anchors()
  82. },
  83. toc: () => {
  84. scroll.toc()
  85. }
  86. }
  87. function mount () {
  88. $('pre').style.display = 'none'
  89. var md = $('pre').innerText
  90. m.mount($('body'), {
  91. oninit: () => {
  92. state.markdown = md
  93. chrome.runtime.sendMessage({
  94. message: 'markdown',
  95. compiler: state.compiler,
  96. markdown: state.markdown
  97. }, (res) => {
  98. state.html = state.content.emoji ? emojinator(res.html) : res.html
  99. m.redraw()
  100. })
  101. },
  102. view: () => {
  103. var dom = []
  104. if (state.raw) {
  105. dom.push(m('pre#_markdown', {oncreate: oncreate.markdown}, state.markdown))
  106. $('body').classList.remove('_toc-left', '_toc-right')
  107. }
  108. else {
  109. if (state.theme) {
  110. var included = Array.from($('body').classList).filter((name) => /^_theme/.test(name))
  111. if (included.length) {
  112. $('body').classList.remove(included)
  113. }
  114. dom.push(m('link#_theme', {
  115. rel: 'stylesheet', type: 'text/css',
  116. href: chrome.runtime.getURL(`/themes/${state.theme}.css`),
  117. }))
  118. $('body').classList.add(`_theme-${state.theme}`)
  119. if (state.content.syntax) {
  120. var prism =
  121. state._themes[state.theme] === 'dark' ||
  122. (state._themes[state.theme] === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)
  123. ? 'prism-okaidia' : 'prism'
  124. dom.push(m('link#_prism', {
  125. rel: 'stylesheet', type: 'text/css',
  126. href: chrome.runtime.getURL(`/vendor/${prism}.min.css`),
  127. }))
  128. }
  129. if (state.content.mermaid) {
  130. mmd.refresh()
  131. }
  132. }
  133. if (state.html) {
  134. dom.push(m('#_html', {oncreate: oncreate.html,
  135. class: (/github(-dark)?/.test(state.theme) ? 'markdown-body' : 'markdown-theme') +
  136. (state.themes.width !== 'auto' ? ` _width-${state.themes.width}` : '')
  137. },
  138. m.trust(state.html)
  139. ))
  140. if (state.content.toc && state.toc) {
  141. dom.push(m('#_toc.tex2jax-ignore', {oncreate: oncreate.toc},
  142. m.trust(state.toc)
  143. ))
  144. $('body').classList.add('_toc-left')
  145. }
  146. if (state.content.mathjax) {
  147. mj.render()
  148. }
  149. }
  150. }
  151. return (dom.length ? dom : m('div'))
  152. }
  153. })
  154. }
  155. var scroll = (() => {
  156. function race (done) {
  157. Promise.race([
  158. Promise.all([
  159. new Promise((resolve) => {
  160. var diagrams = Array.from(document.querySelectorAll('code.language-mmd, code.language-mermaid'))
  161. if (!state.content.mermaid || !diagrams.length) {
  162. resolve()
  163. }
  164. else {
  165. var timeout = setInterval(() => {
  166. var svg = Array.from(document.querySelectorAll('code.language-mmd svg, code.language-mermaid svg'))
  167. if (diagrams.length === svg.length) {
  168. clearInterval(timeout)
  169. resolve()
  170. }
  171. }, 50)
  172. }
  173. }),
  174. new Promise((resolve) => {
  175. var images = Array.from(document.querySelectorAll('img'))
  176. if (!images.length) {
  177. resolve()
  178. }
  179. else {
  180. var loaded = 0
  181. images.forEach((img) => {
  182. img.addEventListener('load', () => {
  183. if (++loaded === images.length) {
  184. resolve()
  185. }
  186. }, {once: true})
  187. })
  188. }
  189. }),
  190. ]),
  191. new Promise((resolve) => setTimeout(resolve, 500))
  192. ])
  193. .then(done)
  194. }
  195. function debounce (container, done) {
  196. var listener = /html|body/i.test(container.nodeName) ? window : container
  197. var timeout = null
  198. listener.addEventListener('scroll', () => {
  199. clearTimeout(timeout)
  200. timeout = setTimeout(done, 100)
  201. })
  202. }
  203. function listen (container, prefix) {
  204. var key = prefix + location.origin + location.pathname
  205. try {
  206. container.scrollTop = parseInt(localStorage.getItem(key))
  207. debounce(container, () => {
  208. localStorage.setItem(key, container.scrollTop)
  209. })
  210. }
  211. catch (err) {
  212. chrome.storage.local.get(key, (res) => {
  213. container.scrollTop = parseInt(res[key])
  214. })
  215. debounce(container, () => {
  216. chrome.storage.local.set({[key]: container.scrollTop})
  217. })
  218. }
  219. }
  220. return {
  221. body: () => {
  222. var loaded
  223. race(() => {
  224. if (!loaded) {
  225. loaded = true
  226. var container = ((html = $('html')) => (
  227. html.scrollTop = 1,
  228. html.scrollTop ? (html.scrollTop = 0, html) : $('body')
  229. ))()
  230. if (state.content.scroll) {
  231. listen(container, 'md-')
  232. }
  233. else if (location.hash && $(location.hash)) {
  234. container.scrollTop = $(location.hash).offsetTop
  235. }
  236. }
  237. })
  238. },
  239. toc: () => {
  240. listen($('#_toc'), 'md-toc-')
  241. }
  242. }
  243. })()
  244. function anchors () {
  245. Array.from($('#_html').childNodes)
  246. .filter((node) => /h[1-6]/i.test(node.tagName))
  247. .forEach((node) => {
  248. var a = document.createElement('a')
  249. a.className = 'anchor'
  250. a.name = node.id
  251. a.href = '#' + node.id
  252. a.innerHTML = '<span class="octicon octicon-link"></span>'
  253. node.prepend(a)
  254. })
  255. }
  256. var toc = (
  257. link = (header) => '<a href="#' + header.id + '">' + header.title + '</a>') =>
  258. Array.from($('#_html').childNodes)
  259. .filter((node) => /h[1-6]/i.test(node.tagName))
  260. .map((node) => ({
  261. id: node.getAttribute('id'),
  262. level: parseInt(node.tagName.replace('H', '')),
  263. title: node.innerText.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  264. }))
  265. .reduce((html, header) => {
  266. html += '<div class="_ul">'.repeat(header.level)
  267. html += link(header)
  268. html += '</div>'.repeat(header.level)
  269. return html
  270. }, '')
  271. if (document.readyState === 'complete') {
  272. mount()
  273. }
  274. else {
  275. var timeout = setInterval(() => {
  276. if (document.readyState === 'complete') {
  277. clearInterval(timeout)
  278. mount()
  279. }
  280. }, 0)
  281. }
  282. if (state.content.autoreload) {
  283. ;(() => {
  284. var initial = ''
  285. var response = (body) => {
  286. if (!initial) {
  287. initial = body
  288. }
  289. else if (initial !== body) {
  290. location.reload(true)
  291. }
  292. }
  293. var xhr = new XMLHttpRequest()
  294. xhr.onreadystatechange = () => {
  295. if (xhr.readyState === 4) {
  296. response(xhr.responseText)
  297. }
  298. }
  299. var get = () => {
  300. if (location.protocol === 'file:') {
  301. chrome.runtime.sendMessage({
  302. message: 'autoreload',
  303. location: location.href
  304. }, (res) => {
  305. if (res.err) {
  306. console.error(res.err)
  307. clearInterval(state.interval)
  308. }
  309. else {
  310. response(res.body)
  311. }
  312. })
  313. }
  314. else {
  315. xhr.open('GET', location.href + '?preventCache=' + Date.now(), true)
  316. try {
  317. xhr.send()
  318. }
  319. catch (err) {
  320. console.error(err)
  321. clearInterval(state.interval)
  322. }
  323. }
  324. }
  325. get()
  326. state.interval = setInterval(get, state.ms)
  327. })()
  328. }