index.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. icon: args.icon,
  9. html: '',
  10. markdown: '',
  11. toc: '',
  12. reload: {
  13. interval: null,
  14. ms: 1000,
  15. md: false,
  16. },
  17. _themes: {
  18. 'github': 'light',
  19. 'github-dark': 'dark',
  20. 'almond': 'light',
  21. // 'air': 'light',
  22. 'awsm': 'light',
  23. 'axist': 'light',
  24. 'bamboo': 'auto',
  25. 'bullframe': 'light',
  26. 'holiday': 'auto',
  27. 'kacit': 'light',
  28. 'latex': 'light',
  29. 'marx': 'light',
  30. 'mini': 'light',
  31. 'modest': 'light',
  32. 'new': 'auto',
  33. 'no-class': 'auto',
  34. 'pico': 'auto',
  35. 'retro': 'dark',
  36. 'sakura': 'light',
  37. 'sakura-vader': 'dark',
  38. 'semantic': 'light',
  39. 'simple': 'auto',
  40. // 'splendor': 'light',
  41. 'style-sans': 'light',
  42. 'style-serif': 'light',
  43. 'stylize': 'light',
  44. 'superstylin': 'auto',
  45. 'tacit': 'light',
  46. 'vanilla': 'auto',
  47. 'water': 'light',
  48. 'water-dark': 'dark',
  49. 'writ': 'light',
  50. }
  51. }
  52. chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
  53. if (req.message === 'reload') {
  54. location.reload(true)
  55. }
  56. else if (req.message === 'theme') {
  57. state.theme = req.theme
  58. m.redraw()
  59. }
  60. else if (req.message === 'themes') {
  61. state.themes = req.themes
  62. m.redraw()
  63. }
  64. else if (req.message === 'raw') {
  65. state.raw = req.raw
  66. m.redraw()
  67. }
  68. else if (req.message === 'autoreload') {
  69. clearInterval(state.reload.interval)
  70. }
  71. })
  72. var oncreate = {
  73. markdown: () => {
  74. setTimeout(() => scroll(), 0)
  75. },
  76. html: () => {
  77. update()
  78. }
  79. }
  80. var onupdate = {
  81. html: () => {
  82. if (state.reload.md) {
  83. state.reload.md = false
  84. update()
  85. }
  86. },
  87. theme: () => {
  88. if (state.content.mermaid) {
  89. setTimeout(() => mmd.render(), 0)
  90. }
  91. }
  92. }
  93. var update = () => {
  94. scroll()
  95. if (state.content.syntax) {
  96. setTimeout(() => Prism.highlightAll(), 20)
  97. }
  98. if (state.content.mermaid) {
  99. setTimeout(() => mmd.render(), 40)
  100. }
  101. if (state.content.mathjax) {
  102. setTimeout(() => mj.render(), 60)
  103. }
  104. }
  105. var render = (md) => {
  106. state.markdown = frontmatter(md)
  107. chrome.runtime.sendMessage({
  108. message: 'markdown',
  109. compiler: state.compiler,
  110. markdown: state.markdown
  111. }, (res) => {
  112. state.html = res.html
  113. if (state.content.emoji) {
  114. state.html = emojinator(state.html)
  115. }
  116. if (state.content.mermaid) {
  117. state.html = state.html.replace(
  118. /<code class="language-(?:mermaid|mmd)">/gi,
  119. '<code class="mermaid">'
  120. )
  121. }
  122. if (state.content.toc) {
  123. state.toc = toc.render(state.html)
  124. }
  125. state.html = anchors(state.html)
  126. m.redraw()
  127. })
  128. }
  129. function mount () {
  130. $('pre').style.display = 'none'
  131. var md = $('pre').innerText
  132. favicon()
  133. m.mount($('body'), {
  134. oninit: () => {
  135. render(md)
  136. },
  137. view: () => {
  138. var dom = []
  139. if (state.raw) {
  140. dom.push(m('pre#_markdown', {oncreate: oncreate.markdown}, state.markdown))
  141. $('body').classList.remove('_toc-left', '_toc-right')
  142. }
  143. else if (state.html) {
  144. var loaded = Array.from($('body').classList).filter((name) => /^_theme/.test(name))[0]
  145. $('body').classList.remove(loaded)
  146. dom.push(m('link#_theme', {
  147. onupdate: onupdate.theme,
  148. rel: 'stylesheet', type: 'text/css',
  149. href: chrome.runtime.getURL(`/themes/${state.theme}.css`),
  150. }))
  151. $('body').classList.add(`_theme-${state.theme}`)
  152. if (state.content.syntax) {
  153. var prism =
  154. state._themes[state.theme] === 'dark' ||
  155. (state._themes[state.theme] === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)
  156. ? 'prism-okaidia' : 'prism'
  157. dom.push(m('link#_prism', {
  158. rel: 'stylesheet', type: 'text/css',
  159. href: chrome.runtime.getURL(`/vendor/${prism}.min.css`),
  160. }))
  161. }
  162. dom.push(m('#_html', {oncreate: oncreate.html, onupdate: onupdate.html,
  163. class: (/github(-dark)?/.test(state.theme) ? 'markdown-body' : 'markdown-theme') +
  164. (state.themes.width !== 'auto' ? ` _width-${state.themes.width}` : '')
  165. },
  166. m.trust(state.html)
  167. ))
  168. if (state.content.toc) {
  169. dom.push(m('#_toc.tex2jax-ignore', m.trust(state.toc)))
  170. $('body').classList.add('_toc-left')
  171. }
  172. }
  173. return dom
  174. }
  175. })
  176. }
  177. var anchors = (html) =>
  178. html.replace(/(<h[1-6] id="(.*?)">)/g, (header, _, id) =>
  179. header +
  180. '<a class="anchor" name="' + id + '" href="#' + id + '">' +
  181. '<span class="octicon octicon-link"></span></a>'
  182. )
  183. var toc = (() => {
  184. var walk = (regex, string, group, result = [], match = regex.exec(string)) =>
  185. !match ? result : walk(regex, string, group, result.concat(!group ? match[1] :
  186. group.reduce((all, name, index) => (all[name] = match[index + 1], all), {})))
  187. return {
  188. render: (html) =>
  189. walk(
  190. /<h([1-6]) id="(.*?)">(.*?)<\/h[1-6]>/gs,
  191. html,
  192. ['level', 'id', 'title']
  193. )
  194. .reduce((toc, {id, title, level}) => toc +=
  195. '<div class="_ul">'.repeat(level) +
  196. '<a href="#' + id + '">' + title.replace(/<a[^>]+>/g, '').replace(/<\/a>/g, '') + '</a>' +
  197. '</div>'.repeat(level)
  198. , '')
  199. }
  200. })()
  201. var frontmatter = (md) => {
  202. if (/^-{3}[\s\S]+?-{3}/.test(md)) {
  203. var [, yaml] = /^-{3}([\s\S]+?)-{3}/.exec(md)
  204. var title = /title: (?:'|")*(.*)(?:'|")*/.exec(yaml)
  205. title && (document.title = title[1])
  206. }
  207. else if (/^\+{3}[\s\S]+?\+{3}/.test(md)) {
  208. var [, toml] = /^\+{3}([\s\S]+?)\+{3}/.exec(md)
  209. var title = /title = (?:'|"|`)*(.*)(?:'|"|`)*/.exec(toml)
  210. title && (document.title = title[1])
  211. }
  212. return md.replace(/^(?:-|\+){3}[\s\S]+?(?:-|\+){3}/, '')
  213. }
  214. var favicon = () => {
  215. var favicon = document.createElement('link')
  216. favicon.rel = 'icon'
  217. favicon.href = chrome.runtime.getURL(`/icons/${state.icon ? 'light' : 'dark'}/16x16.png`)
  218. $('head').appendChild(favicon)
  219. }
  220. if (document.readyState === 'complete') {
  221. mount()
  222. }
  223. else {
  224. var timeout = setInterval(() => {
  225. if (document.readyState === 'complete') {
  226. clearInterval(timeout)
  227. mount()
  228. }
  229. }, 0)
  230. }