cp.scripts.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. // https://github.com/jackmoore/autosize
  2. (function (global, factory) {
  3. if (typeof define === "function" && define.amd) {
  4. define(['module', 'exports'], factory);
  5. } else if (typeof exports !== "undefined") {
  6. factory(module, exports);
  7. } else {
  8. var mod = {
  9. exports: {}
  10. };
  11. factory(mod, mod.exports);
  12. global.autosize = mod.exports;
  13. }
  14. })(this, function (module, exports) {
  15. 'use strict';
  16. var map = typeof Map === "function" ? new Map() : function () {
  17. var keys = [];
  18. var values = [];
  19. return {
  20. has: function has(key) {
  21. return keys.indexOf(key) > -1;
  22. },
  23. get: function get(key) {
  24. return values[keys.indexOf(key)];
  25. },
  26. set: function set(key, value) {
  27. if (keys.indexOf(key) === -1) {
  28. keys.push(key);
  29. values.push(value);
  30. }
  31. },
  32. delete: function _delete(key) {
  33. var index = keys.indexOf(key);
  34. if (index > -1) {
  35. keys.splice(index, 1);
  36. values.splice(index, 1);
  37. }
  38. }
  39. };
  40. }();
  41. var createEvent = function createEvent(name) {
  42. return new Event(name, { bubbles: true });
  43. };
  44. try {
  45. new Event('test');
  46. } catch (e) {
  47. // IE does not support `new Event()`
  48. createEvent = function createEvent(name) {
  49. var evt = document.createEvent('Event');
  50. evt.initEvent(name, true, false);
  51. return evt;
  52. };
  53. }
  54. function assign(ta) {
  55. if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return;
  56. var heightOffset = null;
  57. var clientWidth = null;
  58. var cachedHeight = null;
  59. function init() {
  60. var style = window.getComputedStyle(ta, null);
  61. if (style.resize === 'vertical') {
  62. ta.style.resize = 'none';
  63. } else if (style.resize === 'both') {
  64. ta.style.resize = 'horizontal';
  65. }
  66. if (style.boxSizing === 'content-box') {
  67. heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
  68. } else {
  69. heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
  70. }
  71. // Fix when a textarea is not on document body and heightOffset is Not a Number
  72. if (isNaN(heightOffset)) {
  73. heightOffset = 0;
  74. }
  75. update();
  76. }
  77. function changeOverflow(value) {
  78. {
  79. // Chrome/Safari-specific fix:
  80. // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
  81. // made available by removing the scrollbar. The following forces the necessary text reflow.
  82. var width = ta.style.width;
  83. ta.style.width = '0px';
  84. // Force reflow:
  85. /* jshint ignore:start */
  86. ta.offsetWidth;
  87. /* jshint ignore:end */
  88. ta.style.width = width;
  89. }
  90. ta.style.overflowY = value;
  91. }
  92. function getParentOverflows(el) {
  93. var arr = [];
  94. while (el && el.parentNode && el.parentNode instanceof Element) {
  95. if (el.parentNode.scrollTop) {
  96. arr.push({
  97. node: el.parentNode,
  98. scrollTop: el.parentNode.scrollTop
  99. });
  100. }
  101. el = el.parentNode;
  102. }
  103. return arr;
  104. }
  105. function resize() {
  106. if (ta.scrollHeight === 0) {
  107. // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
  108. return;
  109. }
  110. var overflows = getParentOverflows(ta);
  111. var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
  112. ta.style.height = '';
  113. ta.style.height = ta.scrollHeight + heightOffset + 'px';
  114. // used to check if an update is actually necessary on window.resize
  115. clientWidth = ta.clientWidth;
  116. // prevents scroll-position jumping
  117. overflows.forEach(function (el) {
  118. el.node.scrollTop = el.scrollTop;
  119. });
  120. if (docTop) {
  121. document.documentElement.scrollTop = docTop;
  122. }
  123. }
  124. function update() {
  125. resize();
  126. var styleHeight = Math.round(parseFloat(ta.style.height));
  127. var computed = window.getComputedStyle(ta, null);
  128. // Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box
  129. var actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(computed.height)) : ta.offsetHeight;
  130. // The actual height not matching the style height (set via the resize method) indicates that
  131. // the max-height has been exceeded, in which case the overflow should be allowed.
  132. if (actualHeight < styleHeight) {
  133. if (computed.overflowY === 'hidden') {
  134. changeOverflow('scroll');
  135. resize();
  136. actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
  137. }
  138. } else {
  139. // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
  140. if (computed.overflowY !== 'hidden') {
  141. changeOverflow('hidden');
  142. resize();
  143. actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
  144. }
  145. }
  146. if (cachedHeight !== actualHeight) {
  147. cachedHeight = actualHeight;
  148. var evt = createEvent('autosize:resized');
  149. try {
  150. ta.dispatchEvent(evt);
  151. } catch (err) {
  152. // Firefox will throw an error on dispatchEvent for a detached element
  153. // https://bugzilla.mozilla.org/show_bug.cgi?id=889376
  154. }
  155. }
  156. }
  157. var pageResize = function pageResize() {
  158. if (ta.clientWidth !== clientWidth) {
  159. update();
  160. }
  161. };
  162. var destroy = function (style) {
  163. window.removeEventListener('resize', pageResize, false);
  164. ta.removeEventListener('input', update, false);
  165. ta.removeEventListener('keyup', update, false);
  166. ta.removeEventListener('autosize:destroy', destroy, false);
  167. ta.removeEventListener('autosize:update', update, false);
  168. Object.keys(style).forEach(function (key) {
  169. ta.style[key] = style[key];
  170. });
  171. map.delete(ta);
  172. }.bind(ta, {
  173. height: ta.style.height,
  174. resize: ta.style.resize,
  175. overflowY: ta.style.overflowY,
  176. overflowX: ta.style.overflowX,
  177. wordWrap: ta.style.wordWrap
  178. });
  179. ta.addEventListener('autosize:destroy', destroy, false);
  180. // IE9 does not fire onpropertychange or oninput for deletions,
  181. // so binding to onkeyup to catch most of those events.
  182. // There is no way that I know of to detect something like 'cut' in IE9.
  183. if ('onpropertychange' in ta && 'oninput' in ta) {
  184. ta.addEventListener('keyup', update, false);
  185. }
  186. window.addEventListener('resize', pageResize, false);
  187. ta.addEventListener('input', update, false);
  188. ta.addEventListener('autosize:update', update, false);
  189. ta.style.overflowX = 'hidden';
  190. ta.style.wordWrap = 'break-word';
  191. map.set(ta, {
  192. destroy: destroy,
  193. update: update
  194. });
  195. init();
  196. }
  197. function destroy(ta) {
  198. var methods = map.get(ta);
  199. if (methods) {
  200. methods.destroy();
  201. }
  202. }
  203. function update(ta) {
  204. var methods = map.get(ta);
  205. if (methods) {
  206. methods.update();
  207. }
  208. }
  209. var autosize = null;
  210. // Do nothing in Node.js environment and IE8 (or lower)
  211. if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
  212. autosize = function autosize(el) {
  213. return el;
  214. };
  215. autosize.destroy = function (el) {
  216. return el;
  217. };
  218. autosize.update = function (el) {
  219. return el;
  220. };
  221. } else {
  222. autosize = function autosize(el, options) {
  223. if (el) {
  224. Array.prototype.forEach.call(el.length ? el : [el], function (x) {
  225. return assign(x, options);
  226. });
  227. }
  228. return el;
  229. };
  230. autosize.destroy = function (el) {
  231. if (el) {
  232. Array.prototype.forEach.call(el.length ? el : [el], destroy);
  233. }
  234. return el;
  235. };
  236. autosize.update = function (el) {
  237. if (el) {
  238. Array.prototype.forEach.call(el.length ? el : [el], update);
  239. }
  240. return el;
  241. };
  242. }
  243. exports.default = autosize;
  244. module.exports = exports['default'];
  245. });
  246. // ---
  247. (function(window, $) {
  248. var fave = function(window, $) {
  249. // Private
  250. var FormDataWasChanged = false;
  251. function GetModalAlertTmpl(title, message, error) {
  252. return '<div class="alert alert-' + (!error?'success':'danger') + ' alert-dismissible fade show" role="alert"><strong>' + title + '</strong> ' + message + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>';
  253. };
  254. function ShowSystemMsg(title, message, error) {
  255. var modal_alert_place = $('.modal.show .sys-messages');
  256. if(!modal_alert_place.length) {
  257. modal_alert_place = $('form.alert-here .sys-messages');
  258. }
  259. if(modal_alert_place.length) {
  260. modal_alert_place.html(GetModalAlertTmpl(title, message, error));
  261. }
  262. };
  263. function AjaxDone(data) {
  264. try {
  265. eval(data);
  266. } catch(e) {
  267. if(e instanceof SyntaxError) {
  268. console.log(data);
  269. console.log('Error: JavaScript code eval error', e.message)
  270. }
  271. }
  272. };
  273. function AjaxFail(data, status, error) {
  274. console.log('Error: data sending error, page will be reloaded', data, status, error);
  275. setTimeout(function() {
  276. window.location.reload(false);
  277. }, 1000);
  278. };
  279. function FormToAjax(form) {
  280. form.submit(function(e) {
  281. if(form.hasClass('loading')) {
  282. e.preventDefault();
  283. return;
  284. }
  285. // Block send button
  286. form.addClass('loading').addClass('alert-here');
  287. var button = form.find('button[type=submit]');
  288. button.addClass('progress-bar-striped')
  289. .addClass('progress-bar-animated');
  290. // Another button
  291. if(button.attr('data-target') != '') {
  292. $('#' + button.attr('data-target')).addClass('progress-bar-striped')
  293. .addClass('progress-bar-animated');
  294. }
  295. // Clear form messages
  296. form.find('.sys-messages').html('');
  297. $.ajax({
  298. type: "POST",
  299. url: form.attr('action'),
  300. data: form.serialize()
  301. }).done(function(data) {
  302. FormDataWasChanged = false;
  303. AjaxDone(data)
  304. }).fail(function(xhr, status, error) {
  305. AjaxFail(xhr.responseText, status, error);
  306. }).always(function() {
  307. // Add delay for one second
  308. setTimeout(function() {
  309. form.removeClass('loading').removeClass('alert-here');
  310. button.removeClass('progress-bar-striped')
  311. .removeClass('progress-bar-animated');
  312. // Another button
  313. if(button.attr('data-target') != '') {
  314. $('#' + button.attr('data-target'))
  315. .removeClass('progress-bar-striped')
  316. .removeClass('progress-bar-animated');
  317. }
  318. }, 100);
  319. });
  320. // Prevent submit action
  321. e.preventDefault();
  322. });
  323. // Bind to another button
  324. var button = form.find('button[type=submit]');
  325. if(button.attr('data-target') != '') {
  326. $('#' + button.attr('data-target')).click(function() {
  327. button.click();
  328. });
  329. }
  330. // Mark body if any data in form was changed
  331. if(form.hasClass('prev-data-lost')) {
  332. form.find('input, textarea, select').on('input', function() {
  333. if(!FormDataWasChanged) {
  334. FormDataWasChanged = true;
  335. }
  336. });
  337. }
  338. };
  339. function AllFormsToAjax() {
  340. $('form').each(function() {
  341. FormToAjax($(this));
  342. });
  343. };
  344. function BindWindowBeforeUnload() {
  345. // Prevent page reload if data was changed
  346. $(window).bind('beforeunload', function(){
  347. if(FormDataWasChanged) {
  348. return 'Some data was changed and not saved. Are you sure want to leave page?';
  349. }
  350. });
  351. };
  352. function MakeTextAreasAutoSized() {
  353. autosize($('textarea.autosize'));
  354. }
  355. function Initialize() {
  356. // Check if jQuery was loaded
  357. if(typeof $ == 'function') {
  358. AllFormsToAjax();
  359. BindWindowBeforeUnload();
  360. MakeTextAreasAutoSized();
  361. } else {
  362. console.log('Error: jQuery is not loaded!');
  363. }
  364. };
  365. // Initialize
  366. if(window.addEventListener) {
  367. // W3C standard
  368. window.addEventListener('load', Initialize, false);
  369. } else if(window.attachEvent) {
  370. // Microsoft
  371. window.attachEvent('onload', Initialize);
  372. };
  373. // Public
  374. return {
  375. ShowMsgSuccess: function(title, message) {
  376. ShowSystemMsg(title, message, false);
  377. },
  378. ShowMsgError: function(title, message) {
  379. ShowSystemMsg(title, message, true);
  380. },
  381. ModalUserProfile: function() {
  382. var html = '<div class="modal fade" id="sys-modal-user-settings" tabindex="-1" role="dialog" aria-labelledby="sysModalUserSettingsLabel" aria-hidden="true"> \
  383. <div class="modal-dialog modal-dialog-centered" role="document"> \
  384. <div class="modal-content"> \
  385. <form class="form-user-settings" action="/cp/" method="post" autocomplete="off"> \
  386. <input type="hidden" name="action" value="index-user-update-profile"> \
  387. <div class="modal-header"> \
  388. <h5 class="modal-title" id="sysModalUserSettingsLabel">My profile</h5> \
  389. <button type="button" class="close" data-dismiss="modal" aria-label="Close"> \
  390. <span aria-hidden="true">&times;</span> \
  391. </button> \
  392. </div> \
  393. <div class="modal-body text-left"> \
  394. <div class="form-group"> \
  395. <label for="first_name">First name</label> \
  396. <input type="text" class="form-control" id="first_name" name="first_name" value="' + window.CurrentUserProfileData.first_name + '" placeholder="User first name" autocomplete="off"> \
  397. </div> \
  398. <div class="form-group"> \
  399. <label for="last_name">Last name</label> \
  400. <input type="text" class="form-control" id="last_name" name="last_name" value="' + window.CurrentUserProfileData.last_name + '" placeholder="User last name" autocomplete="off"> \
  401. </div> \
  402. <div class="form-group"> \
  403. <label for="email">Email</label> \
  404. <input type="email" class="form-control" id="email" name="email" value="' + window.CurrentUserProfileData.email + '" placeholder="User email" autocomplete="off" required> \
  405. </div> \
  406. <div class="form-group"> \
  407. <label for="password">New password</label> \
  408. <input type="password" class="form-control" id="password" aria-describedby="passwordHelp" name="password" value="" placeholder="User new password" autocomplete="off"> \
  409. <small id="passwordHelp" class="form-text text-muted">Leave this field empty if you don\'t want change your password</small> \
  410. </div> \
  411. <div class="sys-messages"></div> \
  412. </div> \
  413. <div class="modal-footer"> \
  414. <button type="submit" class="btn btn-primary">Save</button> \
  415. <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> \
  416. </div> \
  417. </form> \
  418. </div> \
  419. </div> \
  420. </div>';
  421. $('#sys-modal-user-settings-placeholder').html(html);
  422. $("#sys-modal-user-settings").modal({
  423. backdrop: 'static',
  424. keyboard: false,
  425. show: false,
  426. });
  427. $('#sys-modal-user-settings').on('hidden.bs.modal', function(e) {
  428. $('#sys-modal-user-settings-placeholder').html('');
  429. });
  430. FormToAjax($('#sys-modal-user-settings form'));
  431. $("#sys-modal-user-settings").modal('show');
  432. },
  433. ActionLogout: function(message) {
  434. if(confirm(message)) {
  435. $.ajax({
  436. type: "POST",
  437. url: '/cp/',
  438. data: {
  439. action: 'index-user-logout',
  440. }
  441. }).done(function(data) {
  442. AjaxDone(data)
  443. }).fail(function(xhr, status, error) {
  444. AjaxFail(xhr.responseText, status, error);
  445. });
  446. }
  447. },
  448. ActionDataTableDelete: function(object, action, id, message) {
  449. if(confirm(message)) {
  450. $.ajax({
  451. type: "POST",
  452. url: '/cp/',
  453. data: {
  454. action: action,
  455. id: id,
  456. }
  457. }).done(function(data) {
  458. AjaxDone(data)
  459. }).fail(function(xhr, status, error) {
  460. AjaxFail(xhr.responseText, status, error);
  461. });
  462. }
  463. },
  464. };
  465. }(window, $);
  466. // Make it public
  467. window.fave = fave;
  468. }(window, jQuery));