options.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. var defaults = {
  2. // storage
  3. origins: {},
  4. header: false,
  5. exclude: [],
  6. // static
  7. protocols: ['https', 'http', '*'],
  8. // UI
  9. protocol: 'https',
  10. origin: '',
  11. domain: '',
  12. timeout: null,
  13. file: true,
  14. }
  15. var state = Object.assign({}, defaults)
  16. var events = {
  17. file: () => {
  18. chrome.tabs.create({url: 'chrome://extensions/?id=' + chrome.runtime.id})
  19. },
  20. header: (e) => {
  21. state.header = !state.header
  22. chrome.runtime.sendMessage({
  23. message: 'options.header',
  24. header: state.header,
  25. })
  26. },
  27. origin: {
  28. protocol: (e) => {
  29. state.protocol = state.protocols[e.target.selectedIndex]
  30. },
  31. name: (e) => {
  32. state.origin = e.target.value
  33. },
  34. add: () => {
  35. var domain = state.origin.replace(/.*:\/\/([^/]+).*/i, '$1')
  36. if (!domain) {
  37. return
  38. }
  39. var origin = state.protocol + '://' + domain
  40. chrome.permissions.request({origins: [origin + '/*']}, (granted) => {
  41. if (granted) {
  42. chrome.runtime.sendMessage({message: 'origin.add', origin}, init)
  43. }
  44. })
  45. },
  46. all: () => {
  47. var origin = '*://*'
  48. chrome.permissions.request({origins: [origin + '/*']}, (granted) => {
  49. if (granted) {
  50. chrome.runtime.sendMessage({message: 'origin.add', origin}, init)
  51. }
  52. })
  53. },
  54. remove: (origin) => () => {
  55. chrome.permissions.remove({origins: [origin + '/*']}, (removed) => {
  56. if (removed) {
  57. chrome.runtime.sendMessage({message: 'origin.remove', origin}, init)
  58. }
  59. })
  60. },
  61. update: (origin) => (e) => {
  62. state.origins[origin] = e.target.value
  63. clearTimeout(state.timeout)
  64. state.timeout = setTimeout(() => {
  65. chrome.runtime.sendMessage({
  66. message: 'origin.update', origin, match: e.target.value
  67. })
  68. }, 750)
  69. },
  70. refresh: (origin) => () => {
  71. chrome.permissions.request({origins: [origin + '/*']})
  72. },
  73. },
  74. domain: {
  75. name: (e) => {
  76. state.domain = e.target.value
  77. },
  78. add: () => {
  79. var domain = state.domain.replace(/.*:\/\/([^/]+).*/i, '$1')
  80. if (!domain) {
  81. return
  82. }
  83. if (state.exclude.includes(domain)) {
  84. return
  85. }
  86. chrome.runtime.sendMessage({
  87. message: 'domain.add',
  88. domain,
  89. }, init)
  90. },
  91. remove: (domain) => () => {
  92. chrome.runtime.sendMessage({
  93. message: 'domain.remove',
  94. index: state.exclude.indexOf(domain),
  95. }, init)
  96. },
  97. defaults: () => {
  98. chrome.runtime.sendMessage({
  99. message: 'domain.defaults',
  100. }, init)
  101. },
  102. },
  103. }
  104. chrome.extension.isAllowedFileSchemeAccess((isAllowedAccess) => {
  105. state.file = /Firefox/.test(navigator.userAgent)
  106. ? true // Allow access to file URLs options isn't working on FF
  107. : isAllowedAccess
  108. m.redraw()
  109. })
  110. var init = () => {
  111. chrome.runtime.sendMessage({message: 'options'}, (res) => {
  112. state = Object.assign({}, defaults, {file: state.file}, res)
  113. m.redraw()
  114. })
  115. }
  116. init()
  117. var oncreate = {
  118. ripple: (vnode) => {
  119. mdc.ripple.MDCRipple.attachTo(vnode.dom)
  120. }
  121. }
  122. var onupdate = {
  123. switch: (vnode) => {
  124. if (vnode.dom.classList.contains('is-checked') !== state.header) {
  125. vnode.dom.classList.toggle('is-checked')
  126. }
  127. }
  128. }
  129. m.mount(document.querySelector('main'), {
  130. view: () =>
  131. m('#options',
  132. // file access is disabled
  133. (!state.file || null) &&
  134. m('.bs-callout m-file',
  135. m('h4.mdc-typography--headline', 'Access to file:// URLs is Disabled'),
  136. m('img.mdc-elevation--z2', {src: '/images/file-urls.png'}),
  137. m('button.mdc-button mdc-button--raised m-button', {
  138. oncreate: oncreate.ripple,
  139. onclick: events.file
  140. },
  141. 'Enable Access to file:// URLs'
  142. )
  143. ),
  144. // allowed origins
  145. m('.bs-callout m-origins',
  146. m('h4.mdc-typography--headline', 'Allowed Origins'),
  147. // add origin
  148. m('select.mdc-elevation--z2 m-select', {
  149. onchange: events.origin.protocol
  150. },
  151. state.protocols.map((protocol) =>
  152. m('option', {
  153. value: protocol
  154. },
  155. protocol + '://'
  156. )
  157. )),
  158. m('.mdc-textfield m-textfield',
  159. m('input.mdc-textfield__input', {
  160. type: 'text',
  161. value: state.origin,
  162. onchange: events.origin.name,
  163. placeholder: 'raw.githubusercontent.com'
  164. })
  165. ),
  166. m('button.mdc-button mdc-button--raised m-button', {
  167. oncreate: oncreate.ripple,
  168. onclick: events.origin.add
  169. },
  170. 'Add'
  171. ),
  172. m('button.mdc-button mdc-button--raised m-button', {
  173. oncreate: oncreate.ripple,
  174. onclick: events.origin.all
  175. },
  176. 'Allow All'
  177. ),
  178. // header detection
  179. m('label.mdc-switch m-switch', {
  180. onupdate: onupdate.switch,
  181. title: 'Toggle header detection'
  182. },
  183. m('input.mdc-switch__native-control', {
  184. type: 'checkbox',
  185. checked: state.header,
  186. onchange: events.header
  187. }),
  188. m('.mdc-switch__background', m('.mdc-switch__knob')),
  189. m('span.mdc-switch-label',
  190. 'Detect ',
  191. m('code', 'text/markdown'),
  192. ' and ',
  193. m('code', 'text/x-markdown'),
  194. ' content type'
  195. )
  196. ),
  197. m('ul.mdc-elevation--z2 m-list',
  198. Object.keys(state.origins).sort().map((origin) =>
  199. m('li',
  200. m('span', origin.replace(/^(\*|file|http(s)?).*/, '$1')),
  201. m('span', origin.replace(/^(\*|file|http(s)?):\/\//, '')),
  202. m('.mdc-textfield m-textfield', {
  203. oncreate: oncreate.textfield
  204. },
  205. m('input.mdc-textfield__input', {
  206. type: 'text',
  207. onkeyup: events.origin.update(origin),
  208. value: state.origins[origin],
  209. })
  210. ),
  211. (origin !== 'file://' || null) &&
  212. m('span',
  213. m('button.mdc-button', {
  214. oncreate: oncreate.ripple,
  215. onclick: events.origin.refresh(origin),
  216. title: 'Refresh'
  217. },
  218. m('i.material-icons icon-refresh')
  219. ),
  220. m('button.mdc-button', {
  221. oncreate: oncreate.ripple,
  222. onclick: events.origin.remove(origin),
  223. title: 'Remove'
  224. },
  225. m('i.material-icons icon-remove')
  226. )
  227. )
  228. )
  229. )
  230. )
  231. ),
  232. // excluded domains
  233. (state.origins['*://*'] || null) &&
  234. m('.bs-callout m-exclude',
  235. m('h4.mdc-typography--headline', 'Excluded Domains'),
  236. m('.mdc-textfield m-textfield',
  237. m('input.mdc-textfield__input', {
  238. type: 'text',
  239. value: state.domain,
  240. onchange: events.domain.name,
  241. placeholder: 'github.com'
  242. })
  243. ),
  244. m('button.mdc-button mdc-button--raised m-button', {
  245. oncreate: oncreate.ripple,
  246. onclick: events.domain.add
  247. },
  248. 'Add'
  249. ),
  250. m('button.mdc-button mdc-button--raised m-button', {
  251. oncreate: oncreate.ripple,
  252. onclick: events.domain.defaults
  253. },
  254. 'Defaults'
  255. ),
  256. m('ul.mdc-elevation--z2 m-list',
  257. state.exclude.sort().map((domain) =>
  258. m('li',
  259. m('span', domain),
  260. (origin !== 'file://' || null) &&
  261. m('span',
  262. m('button.mdc-button', {
  263. oncreate: oncreate.ripple,
  264. onclick: events.domain.remove(domain),
  265. title: 'Remove'
  266. },
  267. m('i.material-icons icon-remove')
  268. )
  269. )
  270. )
  271. )
  272. ),
  273. )
  274. )
  275. })