extra.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
  2. "use strict";
  3. /* globals context, openNotification, xmlHttpRequestJson */
  4. /* jshint esversion:6, strict:global */
  5. //<crypto form (Web login)>
  6. function poormanSalt() { //If crypto.getRandomValues is not available
  7. const base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz';
  8. let text = '$2a$04$';
  9. for (let i = 22; i > 0; i--) {
  10. text += base.charAt(Math.floor(Math.random() * 64));
  11. }
  12. return text;
  13. }
  14. function init_crypto_form() {
  15. /* globals dcodeIO */
  16. const crypto_form = document.getElementById('crypto-form');
  17. if (!crypto_form) {
  18. return;
  19. }
  20. if (!(window.dcodeIO)) {
  21. if (window.console) {
  22. console.log('FreshRSS waiting for bcrypt.js…');
  23. }
  24. setTimeout(init_crypto_form, 100);
  25. return;
  26. }
  27. crypto_form.onsubmit = function (e) {
  28. const submit_button = this.querySelector('button[type="submit"]');
  29. submit_button.disabled = true;
  30. let success = false;
  31. const req = new XMLHttpRequest();
  32. req.open('GET', './?c=javascript&a=nonce&user=' + document.getElementById('username').value, false);
  33. req.onerror = function () {
  34. openNotification('Communication error!', 'bad');
  35. };
  36. req.send();
  37. if (req.status == 200) {
  38. const json = xmlHttpRequestJson(req);
  39. if (!json.salt1 || !json.nonce) {
  40. openNotification('Invalid user!', 'bad');
  41. } else {
  42. try {
  43. const strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'),
  44. s = dcodeIO.bcrypt.hashSync(document.getElementById('passwordPlain').value, json.salt1),
  45. c = dcodeIO.bcrypt.hashSync(json.nonce + s, strong ? dcodeIO.bcrypt.genSaltSync(4) : poormanSalt());
  46. document.getElementById('challenge').value = c;
  47. if (!s || !c) {
  48. openNotification('Crypto error!', 'bad');
  49. } else {
  50. success = true;
  51. }
  52. } catch (ex) {
  53. openNotification('Crypto exception! ' + ex, 'bad');
  54. }
  55. }
  56. } else {
  57. req.onerror();
  58. }
  59. submit_button.disabled = false;
  60. return success;
  61. };
  62. }
  63. //</crypto form (Web login)>
  64. function init_share_observers() {
  65. let shares = document.querySelectorAll('.group-share').length;
  66. const shareAdd = document.querySelector('.share.add');
  67. if (shareAdd) {
  68. shareAdd.onclick = function (ev) {
  69. const s = this.parentElement.querySelector('select'),
  70. opt = s.options[s.selectedIndex];
  71. let row = this.closest('form').getAttribute('data-' + opt.getAttribute('data-form'));
  72. row = row.replace(/##label##/g, opt.text);
  73. row = row.replace(/##type##/g, opt.value);
  74. row = row.replace(/##help##/g, opt.getAttribute('data-help'));
  75. row = row.replace(/##key##/g, shares);
  76. row = row.replace(/##method##/g, opt.getAttribute('data-method'));
  77. row = row.replace(/##field##/g, opt.getAttribute('data-field'));
  78. this.closest('.form-group').insertAdjacentHTML('beforebegin', row);
  79. shares++;
  80. return false;
  81. };
  82. }
  83. }
  84. function init_remove_observers() {
  85. document.querySelectorAll('.post').forEach(function (div) {
  86. div.onclick = function (ev) {
  87. const a = ev.target.closest('a.remove');
  88. if (a) {
  89. const remove_what = a.getAttribute('data-remove');
  90. if (remove_what !== undefined) {
  91. const d = document.getElementById(remove_what);
  92. if (d) {
  93. d.remove();
  94. }
  95. }
  96. return false;
  97. }
  98. };
  99. });
  100. }
  101. function init_feed_observers() {
  102. const s = document.getElementById('category');
  103. if (s && s.matches('select')) {
  104. s.onchange = function (ev) {
  105. const detail = document.getElementById('new_category_name').parentElement;
  106. if (this.value === 'nc') {
  107. detail.setAttribute('aria-hidden', 'false');
  108. detail.querySelector('input').focus();
  109. } else {
  110. detail.setAttribute('aria-hidden', 'true');
  111. }
  112. };
  113. }
  114. }
  115. function init_password_observers() {
  116. document.querySelectorAll('.toggle-password').forEach(function (a) {
  117. a.onmousedown = function (ev) {
  118. const passwordField = document.getElementById(this.getAttribute('data-toggle'));
  119. passwordField.setAttribute('type', 'text');
  120. this.classList.add('active');
  121. return false;
  122. };
  123. a.onmouseup = function (ev) {
  124. const passwordField = document.getElementById(this.getAttribute('data-toggle'));
  125. passwordField.setAttribute('type', 'password');
  126. this.classList.remove('active');
  127. return false;
  128. };
  129. });
  130. }
  131. function init_select_observers() {
  132. document.querySelectorAll('.select-change').forEach(function (s) {
  133. s.onchange = function (ev) {
  134. const opt = s.options[s.selectedIndex],
  135. url = opt.getAttribute('data-url');
  136. if (url) {
  137. s.form.querySelectorAll('[type=submit]').forEach(function (b) {
  138. b.disabled = true;
  139. });
  140. location.href = url;
  141. }
  142. };
  143. });
  144. }
  145. function init_slider_observers() {
  146. const slider = document.getElementById('slider'),
  147. closer = document.getElementById('close-slider');
  148. if (!slider) {
  149. return;
  150. }
  151. document.querySelector('.post').onclick = function (ev) {
  152. const a = ev.target.closest('.open-slider');
  153. if (a) {
  154. if (!context.ajax_loading) {
  155. context.ajax_loading = true;
  156. const req = new XMLHttpRequest();
  157. req.open('GET', a.href + '&ajax=1', true);
  158. req.responseType = 'document';
  159. req.onload = function (e) {
  160. slider.innerHTML = this.response.body.innerHTML;
  161. slider.classList.add('active');
  162. closer.classList.add('active');
  163. context.ajax_loading = false;
  164. };
  165. req.send();
  166. return false;
  167. }
  168. }
  169. };
  170. closer.onclick = function (ev) {
  171. if (data_leave_validation() || confirm(context.i18n.confirmation_default)) {
  172. slider.querySelectorAll('form').forEach(function (f) { f.reset(); });
  173. closer.classList.remove('active');
  174. slider.classList.remove('active');
  175. return true;
  176. } else {
  177. return false;
  178. }
  179. };
  180. }
  181. function data_leave_validation() {
  182. const ds = document.querySelectorAll('[data-leave-validation]');
  183. for (let i = ds.length - 1; i >= 0; i--) {
  184. const input = ds[i];
  185. if (input.type === 'checkbox' || input.type === 'radio') {
  186. if (input.checked != input.getAttribute('data-leave-validation')) {
  187. return false;
  188. }
  189. } else if (input.value != input.getAttribute('data-leave-validation')) {
  190. return false;
  191. }
  192. }
  193. return true;
  194. }
  195. function init_configuration_alert() {
  196. window.onsubmit = function (e) {
  197. window.hasSubmit = true;
  198. };
  199. window.onbeforeunload = function (e) {
  200. if (window.hasSubmit) {
  201. return;
  202. }
  203. if (!data_leave_validation()) {
  204. return false;
  205. }
  206. };
  207. }
  208. function init_extra() {
  209. if (!window.context) {
  210. if (window.console) {
  211. console.log('FreshRSS extra waiting for JS…');
  212. }
  213. window.setTimeout(init_extra, 50); //Wait for all js to be loaded
  214. return;
  215. }
  216. init_crypto_form();
  217. init_share_observers();
  218. init_remove_observers();
  219. init_feed_observers();
  220. init_password_observers();
  221. init_select_observers();
  222. init_slider_observers();
  223. init_configuration_alert();
  224. }
  225. if (document.readyState && document.readyState !== 'loading') {
  226. init_extra();
  227. } else {
  228. document.addEventListener('DOMContentLoaded', function () {
  229. if (window.console) {
  230. console.log('FreshRSS extra waiting for DOMContentLoaded…');
  231. }
  232. init_extra();
  233. }, false);
  234. }
  235. // @license-end