extra.js 6.1 KB

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